Compare commits

..

413 Commits
8.5.x ... main

Author SHA1 Message Date
Fedor Indutny
c0019a1079 v8.19.0-alpha.1
Some checks failed
CI / Danger (push) Has been cancelled
CI / Dependencies (push) Has been cancelled
CI / Lint (push) Has been cancelled
Commits Check / Commit Title Check (push) Has been cancelled
CI / Sticker Creator (push) Has been cancelled
CI / Storybook (push) Has been cancelled
CI / Benchmark (10, 500, 500, 2, 500, largeGroupSendWithBlocks, 50, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
CI / Benchmark (500, 50, 2, 500, largeGroupSend, 20, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
CI / Benchmark (500, groupSend, 100, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
CI / Benchmark (backup, ts/test-mock/benchmarks/backup_bench.node.js) (push) Has been cancelled
CI / Benchmark (callHistorySearch, 100, ts/test-mock/benchmarks/call_history_search_bench.node.js) (push) Has been cancelled
CI / Benchmark (convoOpen, 100, ts/test-mock/benchmarks/convo_open_bench.node.js) (push) Has been cancelled
CI / Benchmark (send, 100, ts/test-mock/benchmarks/send_bench.node.js) (push) Has been cancelled
CI / Benchmark (startup, 10, ts/test-mock/benchmarks/startup_bench.node.js) (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Linux (arm64, ubuntu-22.04-arm64-4-cores) (push) Has been cancelled
CI / Check Min OS Version (ubuntu-latest) (push) Has been cancelled
CI / Check Min OS Version (windows-latest) (push) Has been cancelled
CI / Linux (x64, ubuntu-22.04-8-cores) (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI / Mock Tests (0) (push) Has been cancelled
CI / Mock Tests (1) (push) Has been cancelled
CI / Mock Tests (2) (push) Has been cancelled
CI / Mock Tests (3) (push) Has been cancelled
CI / Check Min OS Version (macos-latest) (push) Has been cancelled
CI / Auto Merge Ready (push) Has been cancelled
2026-06-24 15:22:46 -07:00
Fedor Indutny
21f819184d Update DNS fallback
Some checks failed
CI / Lint (push) Has been cancelled
CI / Sticker Creator (push) Has been cancelled
CI / Danger (push) Has been cancelled
CI / Dependencies (push) Has been cancelled
CI / Storybook (push) Has been cancelled
CI / Benchmark (10, 500, 500, 2, 500, largeGroupSendWithBlocks, 50, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
CI / Benchmark (500, 50, 2, 500, largeGroupSend, 20, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
CI / Benchmark (500, groupSend, 100, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
CI / Benchmark (backup, ts/test-mock/benchmarks/backup_bench.node.js) (push) Has been cancelled
CI / Benchmark (callHistorySearch, 100, ts/test-mock/benchmarks/call_history_search_bench.node.js) (push) Has been cancelled
CI / Benchmark (convoOpen, 100, ts/test-mock/benchmarks/convo_open_bench.node.js) (push) Has been cancelled
CI / Benchmark (send, 100, ts/test-mock/benchmarks/send_bench.node.js) (push) Has been cancelled
CI / Benchmark (startup, 10, ts/test-mock/benchmarks/startup_bench.node.js) (push) Has been cancelled
Commits Check / Commit Title Check (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Linux (arm64, ubuntu-22.04-arm64-4-cores) (push) Has been cancelled
CI / Linux (x64, ubuntu-22.04-8-cores) (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI / Check Min OS Version (macos-latest) (push) Has been cancelled
CI / Check Min OS Version (ubuntu-latest) (push) Has been cancelled
CI / Check Min OS Version (windows-latest) (push) Has been cancelled
CI / Mock Tests (0) (push) Has been cancelled
CI / Mock Tests (1) (push) Has been cancelled
CI / Mock Tests (2) (push) Has been cancelled
CI / Mock Tests (3) (push) Has been cancelled
CI / Auto Merge Ready (push) Has been cancelled
2026-06-24 15:22:45 -07:00
Fedor Indutny
3ee1058f7f Update strings 2026-06-24 15:22:39 -07:00
Fedor Indutny
8107ab3ad8 Release notes for 8.17 2026-06-24 15:22:39 -07:00
Jamie
92012b3546
Init update-signal-symbols script 2026-06-24 14:58:51 -07:00
ayumi-signal
79004c7727
Add donation permit support 2026-06-24 13:58:26 -07:00
ayumi-signal
4492e2f799
Fix deprecated name field in donation config 2026-06-24 20:26:43 +00:00
Fedor Indutny
d553336adc
Fix crash on Linux when calling memoryUsage 2026-06-24 13:24:03 -07:00
Fedor Indutny
86d105dea0
Update copy and UI for MAS 2026-06-24 13:20:50 -07:00
trevor-signal
83617e3ff3
Improve incoming call start behavior 2026-06-24 09:44:42 -07:00
ayumi-signal
3dfc580a92
Ensure consistent sticker pack order when importing from storage 2026-06-24 08:58:20 -07:00
trevor-signal
21bca063a7
Improve handling of invalid title transition and contact messages 2026-06-24 13:39:34 +00:00
andrew-signal
93cbb121f1
Update libsignal to v0.96.3 2026-06-24 01:41:08 +00:00
Fedor Indutny
5331862e44
Update singleArchFiles 2026-06-23 12:34:25 -07:00
trevor-signal
d829a84af9
Quill custom blot improvements 2026-06-24 05:09:59 +10:00
trevor-signal
7c467ef85c
Update pinnedMessageLimit flag name 2026-06-23 18:53:37 +00:00
Fedor Indutny
c6e087b842
Include .node files from updated mac-screen-share 2026-06-23 18:41:15 +00:00
ayumi-signal
83dcbfb143
Update libsignal to v0.96.2 2026-06-24 03:06:35 +10:00
Jamie
72afecc4f3
Wrap labels in AxoStackedButton 2026-06-22 12:23:36 -07:00
Fedor Indutny
2edee6f807
Update mac-screen-share/windows-notifications
Co-authored-by: Backport Bot <backport-bot@signal.org>
2026-06-22 11:13:41 -07:00
Jamie
dfec1ef0fb
accessibility: silence relative timestamp screen reader announcements
Co-authored-by: UlisesMilani <ulisesmilani@gmail.com>
Co-authored-by: Backport Bot <backport-bot@signal.org>
2026-06-22 11:01:24 -07:00
Jamie
eab19262cb
Upgrade audit deps 2026-06-22 10:48:25 -07:00
Fedor Indutny
a36e7aad48
Update sqlcipher to 3.3.9 2026-06-18 10:55:51 -07:00
trevor-signal
90ae26db67
Fix test-electron glob to include .tsx files 2026-06-18 11:47:18 -04:00
trevor-signal
4c4b2aa353
Update local call history when call is auto-rejected 2026-06-17 16:59:46 -07:00
ayumi-signal
240a262285
Fix MediaEditor CompositionInput enter key so it saves changes 2026-06-17 16:59:28 -07:00
Jamie Kyle
c6996e6184 v8.18.0-alpha.1
Some checks failed
CI / Dependencies (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Sticker Creator (push) Has been cancelled
CI / Danger (push) Has been cancelled
CI / Storybook (push) Has been cancelled
CI / Benchmark (10, 500, 500, 2, 500, largeGroupSendWithBlocks, 50, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
CI / Benchmark (500, 50, 2, 500, largeGroupSend, 20, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
CI / Benchmark (500, groupSend, 100, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
CI / Benchmark (backup, ts/test-mock/benchmarks/backup_bench.node.js) (push) Has been cancelled
CI / Benchmark (callHistorySearch, 100, ts/test-mock/benchmarks/call_history_search_bench.node.js) (push) Has been cancelled
CI / Benchmark (convoOpen, 100, ts/test-mock/benchmarks/convo_open_bench.node.js) (push) Has been cancelled
CI / Benchmark (send, 100, ts/test-mock/benchmarks/send_bench.node.js) (push) Has been cancelled
CI / Benchmark (startup, 10, ts/test-mock/benchmarks/startup_bench.node.js) (push) Has been cancelled
Commits Check / Commit Title Check (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Linux (arm64, ubuntu-22.04-arm64-4-cores) (push) Has been cancelled
CI / Mock Tests (1) (push) Has been cancelled
CI / Linux (x64, ubuntu-22.04-8-cores) (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI / Mock Tests (0) (push) Has been cancelled
CI / Mock Tests (2) (push) Has been cancelled
CI / Mock Tests (3) (push) Has been cancelled
CI / Check Min OS Version (macos-latest) (push) Has been cancelled
CI / Check Min OS Version (ubuntu-latest) (push) Has been cancelled
CI / Check Min OS Version (windows-latest) (push) Has been cancelled
CI / Auto Merge Ready (push) Has been cancelled
2026-06-17 16:03:57 -07:00
Jamie Kyle
c80b7e2744 Update DNS fallback
Some checks failed
CI / Dependencies (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Sticker Creator (push) Has been cancelled
CI / Danger (push) Has been cancelled
CI / Storybook (push) Has been cancelled
CI / Benchmark (10, 500, 500, 2, 500, largeGroupSendWithBlocks, 50, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
CI / Benchmark (500, 50, 2, 500, largeGroupSend, 20, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
CI / Benchmark (500, groupSend, 100, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
CI / Benchmark (backup, ts/test-mock/benchmarks/backup_bench.node.js) (push) Has been cancelled
CI / Benchmark (callHistorySearch, 100, ts/test-mock/benchmarks/call_history_search_bench.node.js) (push) Has been cancelled
CI / Benchmark (convoOpen, 100, ts/test-mock/benchmarks/convo_open_bench.node.js) (push) Has been cancelled
CI / Benchmark (send, 100, ts/test-mock/benchmarks/send_bench.node.js) (push) Has been cancelled
CI / Benchmark (startup, 10, ts/test-mock/benchmarks/startup_bench.node.js) (push) Has been cancelled
Commits Check / Commit Title Check (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Linux (arm64, ubuntu-22.04-arm64-4-cores) (push) Has been cancelled
CI / Linux (x64, ubuntu-22.04-8-cores) (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI / Mock Tests (0) (push) Has been cancelled
CI / Mock Tests (1) (push) Has been cancelled
CI / Mock Tests (2) (push) Has been cancelled
CI / Mock Tests (3) (push) Has been cancelled
CI / Check Min OS Version (macos-latest) (push) Has been cancelled
CI / Check Min OS Version (ubuntu-latest) (push) Has been cancelled
CI / Check Min OS Version (windows-latest) (push) Has been cancelled
CI / Auto Merge Ready (push) Has been cancelled
2026-06-17 16:03:57 -07:00
Jamie Kyle
55c5c00e26 Update strings 2026-06-17 16:03:02 -07:00
Jamie Kyle
9be0fabd94 Update release notes 2026-06-17 16:03:02 -07:00
trevor-signal
97721dff60
Update to latest AccountRecord proto 2026-06-17 14:57:36 -04:00
trevor-signal
4aca3b9445
Avoid toasting on remote config changes when linking as new device 2026-06-17 14:54:38 -04:00
Jamie
80fb55fed0
Add toast for sticker creator for invalid file formats 2026-06-17 11:51:16 -07:00
Jamie
cb4fac7bb4
Fix highlight when scrolling to unloaded pinned message 2026-06-17 11:17:36 -07:00
Fedor Indutny
49d87a5f6f
Re-enable requestSingleInstanceLock for MAS 2026-06-17 17:30:58 +00:00
Jamie
6c37a4005b
Remove plaintextExport feature flag 2026-06-17 10:18:04 -07:00
Jamie
86c2a8420d
Fix jumbomoji for messages with whitespace 2026-06-17 10:17:13 -07:00
Fedor Indutny
d0235ff8f1
Fix dependency errors 2026-06-17 10:14:25 -07:00
trevor-signal
cfe2193db0
Harden attachment file ref-counting when reusing files on disk 2026-06-17 12:52:25 -04:00
trevor-signal
7b1db7325c
Request backfill and skip queueing for undownloadable attachments 2026-06-17 13:55:11 +00:00
Jamie
39a851c4c4
Fix sticker creator localization
Co-authored-by: Fedor Indutny <indutny@signal.org>
2026-06-17 05:18:46 +00:00
Jamie
734f710345
Disable text selection in sticker creator
Co-authored-by: Fedor Indutny <indutny@signal.org>
2026-06-17 05:15:08 +00:00
Fedor Indutny
12ba35bcd5
Update yauzl to 3.4.0 2026-06-16 22:31:29 +00:00
trevor-signal
9ba4145f00
Pin node-gyp to fix windows CI
Co-authored-by: Fedor Indutny <indutny@signal.org>
2026-06-16 22:18:49 +00:00
trevor-signal
a23020d18a
Fix document ordering in All Media gallery 2026-06-15 15:36:12 -07:00
Fedor Indutny
4c3ec21815
More localization strings for MAS 2026-06-15 15:35:47 -07:00
Fedor Indutny
e74e4d1e02
Simplify CI dependencies 2026-06-15 15:19:14 -07:00
Fedor Indutny
128e6e1e5e
Get/push MAS strings 2026-06-15 13:45:30 -07:00
Fedor Indutny
8853dead2b
Fix boolean condition in remove-strings 2026-06-15 13:42:13 -07:00
Fedor Indutny
fd1e60d46e
Add package.json version tag for MAS builds
Co-authored-by: ayumi-signal <143036029+ayumi-signal@users.noreply.github.com>
2026-06-15 12:47:30 -07:00
trevor-signal
04a18e7812
Show system contact icon in GroupMembershipList 2026-06-13 04:34:03 +10:00
trevor-signal
ab91e69cac
Respect global pinned chat remoteConfig value 2026-06-13 04:13:30 +10:00
Fedor Indutny
8140261b5b
Support building for MAS 2026-06-12 09:59:04 -07:00
Fedor Indutny
01dde9aac3
Move windows-ucv into the monorepo 2026-06-11 16:05:04 -07:00
trevor-signal
1db0ad06b2
Simplify recovery key warning dialogs 2026-06-11 13:13:25 -07:00
ayumi-signal
12c7df49f3
Refresh StickerManager to use design system components
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
2026-06-11 13:13:13 -07:00
Fedor Indutny
108241e477 v8.17.0-alpha.1
Some checks failed
Benchmark / Benchmark (10, 500, 500, 2, 500, largeGroupSendWithBlocks, 50, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (500, 50, 2, 500, largeGroupSend, 20, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (500, groupSend, 100, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (backup, ts/test-mock/benchmarks/backup_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (callHistorySearch, 100, ts/test-mock/benchmarks/call_history_search_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (convoOpen, 100, ts/test-mock/benchmarks/convo_open_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (send, 100, ts/test-mock/benchmarks/send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (startup, 10, ts/test-mock/benchmarks/startup_bench.node.js) (push) Has been cancelled
CI / Dependencies (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Sticker Creator (push) Has been cancelled
Commits Check / Commit Title Check (push) Has been cancelled
Stories / Storybook (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Linux (arm64, ubuntu-22.04-arm64-4-cores) (push) Has been cancelled
CI / Linux (x64, ubuntu-22.04-8-cores) (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI / Mock Tests (0) (push) Has been cancelled
CI / Mock Tests (1) (push) Has been cancelled
CI / Mock Tests (2) (push) Has been cancelled
CI / Mock Tests (3) (push) Has been cancelled
CI / Check Min OS Version (macos-latest) (push) Has been cancelled
CI / Check Min OS Version (ubuntu-latest) (push) Has been cancelled
CI / Check Min OS Version (windows-latest) (push) Has been cancelled
2026-06-10 14:38:25 -07:00
Fedor Indutny
f5020bde61 Update DNS fallback
Some checks failed
Benchmark / Benchmark (10, 500, 500, 2, 500, largeGroupSendWithBlocks, 50, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (500, 50, 2, 500, largeGroupSend, 20, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (500, groupSend, 100, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (backup, ts/test-mock/benchmarks/backup_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (callHistorySearch, 100, ts/test-mock/benchmarks/call_history_search_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (convoOpen, 100, ts/test-mock/benchmarks/convo_open_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (send, 100, ts/test-mock/benchmarks/send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (startup, 10, ts/test-mock/benchmarks/startup_bench.node.js) (push) Has been cancelled
CI / Dependencies (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Sticker Creator (push) Has been cancelled
Commits Check / Commit Title Check (push) Has been cancelled
Stories / Storybook (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Linux (arm64, ubuntu-22.04-arm64-4-cores) (push) Has been cancelled
CI / Linux (x64, ubuntu-22.04-8-cores) (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI / Mock Tests (0) (push) Has been cancelled
CI / Mock Tests (1) (push) Has been cancelled
CI / Mock Tests (2) (push) Has been cancelled
CI / Mock Tests (3) (push) Has been cancelled
CI / Check Min OS Version (macos-latest) (push) Has been cancelled
CI / Check Min OS Version (ubuntu-latest) (push) Has been cancelled
CI / Check Min OS Version (windows-latest) (push) Has been cancelled
2026-06-10 14:38:25 -07:00
Fedor Indutny
16239c00b8 Update strings 2026-06-10 14:37:50 -07:00
Fedor Indutny
a4888e69bf Release notes for 8.15 2026-06-10 14:37:50 -07:00
Fedor Indutny
2a437ce2e2
Revert "Update electron to 42.4.0" 2026-06-10 18:44:44 +00:00
trevor-signal
8d32b9cf9d
Autofocus on close button when opening lightbox 2026-06-09 22:56:26 -04:00
trevor-signal
4e6edd921a
Require confirmation on recovery key paste 2026-06-09 22:43:36 -04:00
andrew-signal
cb9b9cacaf
Update libsignal to v0.95.0
Co-authored-by: Fedor Indutny <indutny@signal.org>
2026-06-10 00:46:27 +00:00
emir-signal
e11f4e8651
Update to RingRTC v2.69.3 2026-06-09 17:16:05 -07:00
ayumi-signal
48f7e72cbd
Linux AppImage: Use static runtime 2026-06-09 14:28:07 -07:00
Fedor Indutny
6d47e89e38
Update electron to 42.4.0 2026-06-09 13:56:21 -07:00
trevor-signal
6a31fbc05c
Improve error dialogs during local backups setup 2026-06-09 16:36:09 -04:00
Jamie
8c5073512f
Add SKIP_VERIFY_DEPS_BEFORE_RUN option to pnpm tasks 2026-06-09 12:49:00 -07:00
Fedor Indutny
d7082bad17
Add 5% jitter to CheckScheduler 2026-06-09 18:51:28 +00:00
trevor-signal
bd9a23c5ef
Improve call screen header buttons and context menu 2026-06-09 12:39:14 -04:00
Jamie
6914bf38f9
Fix padding of edit history dialog 2026-06-09 11:11:23 +10:00
Jamie
21e2b03a92
Upgrade pnpm 2026-06-08 13:12:24 -07:00
trevor-signal
e1e09e3a8b
Fix isPermanentlyUndownloadable check for outgoing attachments 2026-06-08 12:29:36 -07:00
ayumi-signal
ffd3b9ea35
Save JPEG files with jpg extension and update default filename 2026-06-08 14:33:30 -04:00
trevor-signal
2f772e96ab
Add warning dialog when copying recovery key 2026-06-05 13:38:53 -04:00
ayumi-signal
4eddde9922
Fix padding in CallLinkEditModal 2026-06-05 13:36:38 -04:00
trevor-signal
add9b36540
Use libsignal API for 1:1 sends 2026-06-05 09:18:33 +10:00
ayumi-signal
34dd2c7f70
[signalapp/Signal-Desktop#7904] Skip edit-previous shortcut during IME composition
Co-authored-by: ayu <ayu@ayu.dev>
2026-06-04 11:17:10 -07:00
ayumi-signal
8e1728ff63
[signalapp/Signal-Desktop#7202] Add Linux arm64 to CI pipeline
Co-authored-by: Dennis Ameling <dennis@dennisameling.com>
2026-06-04 10:33:07 -07:00
ayumi-signal
37b5b39a3e
Update sticker pack link preview in chat and composer
Co-authored-by: Jamie Kyle <jamie@signal.org>
2026-06-03 17:47:08 -07:00
ayumi-signal
a0af83d748 v8.16.0-alpha.1
Some checks failed
Benchmark / Benchmark (convoOpen, 100, ts/test-mock/benchmarks/convo_open_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (send, 100, ts/test-mock/benchmarks/send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (startup, 10, ts/test-mock/benchmarks/startup_bench.node.js) (push) Has been cancelled
CI / Lint (push) Has been cancelled
Commits Check / Commit Title Check (push) Has been cancelled
Stories / Storybook (push) Has been cancelled
Benchmark / Benchmark (10, 500, 500, 2, 500, largeGroupSendWithBlocks, 50, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (500, 50, 2, 500, largeGroupSend, 20, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (500, groupSend, 100, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (backup, ts/test-mock/benchmarks/backup_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (callHistorySearch, 100, ts/test-mock/benchmarks/call_history_search_bench.node.js) (push) Has been cancelled
CI / Sticker Creator (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Linux (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI / Check Min OS Version (macos-latest) (push) Has been cancelled
CI / Mock Tests (0) (push) Has been cancelled
CI / Mock Tests (1) (push) Has been cancelled
CI / Mock Tests (2) (push) Has been cancelled
CI / Mock Tests (3) (push) Has been cancelled
CI / Check Min OS Version (ubuntu-latest) (push) Has been cancelled
CI / Check Min OS Version (windows-latest) (push) Has been cancelled
2026-06-03 14:40:46 -07:00
ayumi-signal
9103655a00 Update DNS fallback
Some checks failed
Benchmark / Benchmark (500, groupSend, 100, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (backup, ts/test-mock/benchmarks/backup_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (callHistorySearch, 100, ts/test-mock/benchmarks/call_history_search_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (convoOpen, 100, ts/test-mock/benchmarks/convo_open_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (send, 100, ts/test-mock/benchmarks/send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (startup, 10, ts/test-mock/benchmarks/startup_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (10, 500, 500, 2, 500, largeGroupSendWithBlocks, 50, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (500, 50, 2, 500, largeGroupSend, 20, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Sticker Creator (push) Has been cancelled
Commits Check / Commit Title Check (push) Has been cancelled
Stories / Storybook (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Linux (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI / Mock Tests (0) (push) Has been cancelled
CI / Mock Tests (1) (push) Has been cancelled
CI / Mock Tests (2) (push) Has been cancelled
CI / Mock Tests (3) (push) Has been cancelled
CI / Check Min OS Version (macos-latest) (push) Has been cancelled
CI / Check Min OS Version (ubuntu-latest) (push) Has been cancelled
CI / Check Min OS Version (windows-latest) (push) Has been cancelled
2026-06-03 14:40:46 -07:00
ayumi-signal
2239a46163 Update strings 2026-06-03 14:40:46 -07:00
ayumi-signal
3a9a8bf7a8 Release notes for 8.14 2026-06-03 14:40:45 -07:00
Fedor Indutny
f39a576b67
Add usernameChangeSyncMessage capability 2026-06-03 10:50:23 -07:00
Scott Nonnenberg
866ca8e830
getConversationForTargetMessage: Search in-memory messages first 2026-06-03 09:51:26 -04:00
andrew-signal
f00b79e3e2
Bump libsignal-client to 0.94.4 2026-06-02 15:36:11 -07:00
ayumi-signal
1dbb748c9c
Adjust background color of hand icons in CallingRaisedHandsList 2026-06-01 16:16:57 -07:00
Fedor Indutny
9daca313d3
Update macos actions runner to 26 2026-06-01 13:31:59 -07:00
Fedor Indutny
851d8bf39f
Use svg icon on macOS 2026-06-01 12:46:37 -07:00
trevor-signal
b88af5db21
Avoid quoting message on double-click in reaction picker 2026-06-01 13:58:15 -04:00
andrew-signal
fa3634c6d9
Bump to libsignal v0.94.3 2026-06-01 10:33:18 -07:00
trevor-signal
706c0020cc
Don't show lonely-in-group messages as read 2026-06-01 09:35:04 -04:00
ayumi-signal
2ddc8fc5fd
Update sticker preview modal and fix usage in sticker manager 2026-05-29 15:22:17 -07:00
trevor-signal
99f704363d
Guard against invalid extensions during chat export 2026-05-30 06:30:36 +10:00
trevor-signal
89550b31fe
Refactor attachment download error states 2026-05-29 10:57:54 -07:00
trevor-signal
433437df27
Filter out unknown recipients in storage service records 2026-05-30 03:46:39 +10:00
trevor-signal
b5bfaff82d
Improve proxy agent log 2026-05-29 10:36:34 -07:00
trevor-signal
7ec0d0e577
Ensure emoji picker is clickable when dialogs are open 2026-05-29 10:00:10 -04:00
trevor-signal
0e2c07324f
Properly encode cdnKey as URI component 2026-05-28 14:52:13 -04:00
trevor-signal
a11ba29c48
Improve too-long message body checks 2026-05-28 10:57:28 -07:00
Fedor Indutny
a1dfc702d9
Update electron-builder to 26.11.1 2026-05-27 16:42:09 -07:00
Scott Nonnenberg
bd36b19a84 v8.15.0-alpha.1
Some checks failed
Benchmark / Benchmark (10, 500, 500, 2, 500, largeGroupSendWithBlocks, 50, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (500, 50, 2, 500, largeGroupSend, 20, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (500, groupSend, 100, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (backup, ts/test-mock/benchmarks/backup_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (callHistorySearch, 100, ts/test-mock/benchmarks/call_history_search_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (convoOpen, 100, ts/test-mock/benchmarks/convo_open_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (send, 100, ts/test-mock/benchmarks/send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (startup, 10, ts/test-mock/benchmarks/startup_bench.node.js) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Sticker Creator (push) Has been cancelled
Commits Check / Commit Title Check (push) Has been cancelled
Stories / Storybook (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Linux (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI / Mock Tests (0) (push) Has been cancelled
CI / Mock Tests (1) (push) Has been cancelled
CI / Mock Tests (2) (push) Has been cancelled
CI / Mock Tests (3) (push) Has been cancelled
CI / Check Min OS Version (macos-latest) (push) Has been cancelled
CI / Check Min OS Version (ubuntu-latest) (push) Has been cancelled
CI / Check Min OS Version (windows-latest) (push) Has been cancelled
2026-05-27 14:10:16 -07:00
Scott Nonnenberg
42097d7bd8 Update DNS fallback
Some checks failed
Commits Check / Commit Title Check (push) Has been cancelled
Benchmark / Benchmark (convoOpen, 100, ts/test-mock/benchmarks/convo_open_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (send, 100, ts/test-mock/benchmarks/send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (startup, 10, ts/test-mock/benchmarks/startup_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (10, 500, 500, 2, 500, largeGroupSendWithBlocks, 50, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (500, 50, 2, 500, largeGroupSend, 20, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (500, groupSend, 100, ts/test-mock/benchmarks/group_send_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (backup, ts/test-mock/benchmarks/backup_bench.node.js) (push) Has been cancelled
Benchmark / Benchmark (callHistorySearch, 100, ts/test-mock/benchmarks/call_history_search_bench.node.js) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Sticker Creator (push) Has been cancelled
Stories / Storybook (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Linux (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI / Mock Tests (0) (push) Has been cancelled
CI / Mock Tests (1) (push) Has been cancelled
CI / Mock Tests (2) (push) Has been cancelled
CI / Mock Tests (3) (push) Has been cancelled
CI / Check Min OS Version (macos-latest) (push) Has been cancelled
CI / Check Min OS Version (ubuntu-latest) (push) Has been cancelled
CI / Check Min OS Version (windows-latest) (push) Has been cancelled
2026-05-27 14:10:15 -07:00
Scott Nonnenberg
55f73e702c Update strings 2026-05-27 14:10:15 -07:00
Scott Nonnenberg
88f989cba5 Release notes for 8.13 2026-05-27 14:10:15 -07:00
Scott Nonnenberg
ca0990c001
sendProfileKey: Send in response to fewer incoming messages 2026-05-28 04:31:09 +10:00
Fedor Indutny
8aea456151
Update electron to 42.3.0 2026-05-27 11:15:14 -07:00
adel-signal
4ab8ef745d
Update to RingRTC v2.69.1
Co-authored-by: Fedor Indutny <indutny@signal.org>
2026-05-26 16:45:07 -07:00
trevor-signal
d4720fec1e
Bring StorageProto.AccountRecord up-to-date 2026-05-26 14:49:30 -07:00
Jamie
5088d02e01
Setup scheduled WAL checkpoints 2026-05-26 14:26:43 -07:00
trevor-signal
02bd907768
Preserve reactions for view once messages 2026-05-26 13:54:43 -07:00
Fedor Indutny
865d37de64
Update protopiler to 4.0.2 2026-05-26 09:48:51 -07:00
trevor-signal
0353eaf61f
Avoid rename across partitions during local backup 2026-05-27 02:05:54 +10:00
Jamie
2524f83079
Fix type errors and disable typescript incremental mode 2026-05-26 09:45:19 -04:00
Jamie
2c3ddcd6b9
Migrate more dialogs to new design system 2026-05-22 09:34:34 -07:00
Jamie
c83efc3b6e
Update all type methods to use property syntax 2026-05-22 07:12:56 -07:00
trevor-signal
a7e78e9a7f
Check directly for selection in isStyleEnabledInSelection 2026-05-21 13:58:36 -07:00
Jamie
0cc5bb1b50
Init AxoStackedButton 2026-05-20 14:55:20 -07:00
trevor-signal
a8f91c2c20 v8.14.0-alpha.1 2026-05-20 15:56:31 -04:00
trevor-signal
d7e25ff415 Update DNS fallback 2026-05-20 15:56:30 -04:00
trevor-signal
ff358a59d1 Update strings 2026-05-20 15:56:29 -04:00
trevor-signal
ab4d2190e8 Release notes for 8.12 2026-05-20 15:56:29 -04:00
Jamie
e2628dc7f3
Revert quill keybindings using codes 2026-05-20 15:44:43 -04:00
trevor-signal
ca5100f602
Fix quill behavior on blur 2026-05-20 12:58:18 -04:00
trevor-signal
410216e2ae
Update KT failure and unavailable modal strings 2026-05-20 09:46:45 -07:00
ayumi-signal
5ee6a0c1b6
Reproducible builds action: add contents read access 2026-05-20 10:32:04 -04:00
trevor-signal
3f871dcbaa
Update download click area for visual attachments 2026-05-20 08:20:47 +10:00
Jamie
a949293617
Upgrade tinykeys and fix cmd+backspace creating bold text 2026-05-20 07:53:24 +10:00
trevor-signal
7055569291
Reset KT fields when username or E164 change 2026-05-20 07:51:47 +10:00
trevor-signal
5806c75882
Fix callscreen flash in light mode 2026-05-19 10:04:26 -07:00
Jamie
89f418b11f
Upgrade build dependencies 2026-05-19 07:53:50 +10:00
Jamie
1ffe46f587
Remove group send endorsement toasts 2026-05-18 14:40:54 -07:00
gram-signal
ef970f74c8
Remote config for requirePqRatio 2026-05-18 16:23:50 -04:00
Jamie
57aa58506d
Init build:db-schema script 2026-05-18 10:42:50 -07:00
andrew-signal
4bd35d7378
Bump libsignal-client to 0.94.1 2026-05-18 10:01:28 -07:00
trevor-signal
81130ec712
Add internal toast on remote config changes 2026-05-18 09:50:17 -07:00
ayumi-signal
ac252557ac
Add raised hand order to raised hands list 2026-05-18 09:45:58 -07:00
trevor-signal
0c4b7fc537
Add toggle raised hand shortcut in group calls 2026-05-18 09:40:43 -07:00
trevor-signal
65db74055d
Improve release note channel behavior 2026-05-18 10:50:23 -04:00
trevor-signal
fd19b59561
Update headers on websocket requests 2026-05-18 10:48:27 -04:00
trevor-signal
a106a82488
Sync release note channel metadata in storage service 2026-05-16 08:07:32 +10:00
Jamie
da8bed8b23
Allow AxoTooltip to be disabled 2026-05-15 16:49:35 -04:00
ayumi-signal
e1a624bde5
Create issues when reproducible builds fail 2026-05-15 10:51:19 -07:00
Jamie
1cfc6c572b
Fix quill keybindings on non-US keyboard layouts 2026-05-15 09:25:02 +10:00
Jamie
8a6dd0b11a
Fix link previews after emoji 2026-05-15 09:22:13 +10:00
Jamie Kyle
0304dbc5a6 v8.13.0-alpha.1 2026-05-14 12:05:08 -07:00
Jamie Kyle
8686ed8f27 Update DNS fallback 2026-05-14 12:05:08 -07:00
Jamie Kyle
20586294d4 Update strings 2026-05-14 12:05:07 -07:00
Jamie Kyle
d25e9b6bff Update release notes 2026-05-14 12:05:07 -07:00
Jamie
6b6bd053be
Remove adminKey assert in toCallLinkRecord 2026-05-14 11:20:53 -07:00
Jamie
5fc45209f9
Fix contact nickname in story reaction notification 2026-05-14 11:13:32 -07:00
trevor-signal
58be82c01a
Update file path redactions 2026-05-14 11:08:06 -07:00
Jamie
3951a058a8
Allow fonts from 'asset:' urls in created windows
Signed-off-by: Marcin Serwin <marcin@serwin.dev>
Co-authored-by: Marcin Serwin <marcin@serwin.dev>
2026-05-14 11:01:40 -07:00
Jamie
bf4bfbdaad
Fix emoji search ranking for default short name 2026-05-14 11:29:09 -04:00
Jamie
90194e39c2
Update composition area buttons and menus with axo components 2026-05-13 22:47:57 -07:00
Jamie
346c483787
Fix pnpm patch failures 2026-05-13 17:36:07 -07:00
Jamie
958f754c96
Make call link notice text unselectable 2026-05-14 09:36:49 +10:00
trevor-signal
38304b2146
Improve CompositionArea behavior in strict mode 2026-05-13 15:51:12 -07:00
Jamie
44332410d8
Always hide voice note record button with edit 2026-05-13 14:57:09 -07:00
ayumi-signal
0ea3b1bbc7
Add experimental support for building Linux ARM64
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
2026-05-13 10:21:25 -07:00
Jamie
00e5282361
Fix emoji rendering and copying issues 2026-05-12 15:32:20 -07:00
ayumi-signal
0aa27d3d9b
Fix and deprecate usePrevious hook and update raised hands button 2026-05-12 14:37:58 -07:00
trevor-signal
6482dff544
Fixup mute options and clarify setMuteDuration 2026-05-12 16:31:26 -04:00
Scott Nonnenberg
de4ce71aba
Storage: Only take profileKey/profileName if no data or first sync 2026-05-12 12:40:53 -04:00
trevor-signal
6cf6d2ebee
Adjust date header styles 2026-05-11 17:01:26 -04:00
Fedor Indutny
568925f0f7
Further improvements for voice recording 2026-05-11 13:24:12 -07:00
andrew-signal
03dd5f2b9f
Bump libsignal-client to 0.94.0 2026-05-11 11:27:19 -07:00
Fedor Indutny
55b24e25af
Fix StoryViewer not progressing 2026-05-11 10:15:42 -07:00
adel-signal
d47554abdd
Bump RingRTC version to v2.69.0 2026-05-11 10:01:00 -07:00
trevor-signal
e15c3adddc
Improved relink prompt
Co-authored-by: Scott Nonnenberg <scott@signal.org>
2026-05-11 10:30:56 -04:00
Jamie
e0ef26bb4f
Migrate simple modals to Axo dialogs 2026-05-08 13:50:18 -07:00
Scott Nonnenberg
c8acf86e6f
ourProfileKey.doGet: Wait no more than 30 seconds for blocking promises 2026-05-09 06:14:10 +10:00
Jamie
1bf2664b58
Migrate Alert to AxoConfirmDialog 2026-05-08 13:12:11 -07:00
trevor-signal
7c68acfb1e
Use CORS for more images 2026-05-08 09:48:20 -07:00
trevor-signal
37341680a6
Ensure static stickers load with CORS 2026-05-08 12:10:31 -04:00
Scott Nonnenberg
1b2a3e7b28 v8.12.0-alpha.1 2026-05-07 15:22:47 -07:00
Scott Nonnenberg
6f1a6ace98 Update DNS fallback 2026-05-07 15:22:46 -07:00
Scott Nonnenberg
6441519468 Update strings 2026-05-07 15:22:46 -07:00
Scott Nonnenberg
b73d17d69d Release notes for 8.10 2026-05-07 15:22:46 -07:00
Scott Nonnenberg
eecb7bf966
sendToGroup: Ensure endorsements are fetched with alreadyInQueue=true 2026-05-08 08:06:55 +10:00
ayumi-signal
b0ed5f4a9b
Fix local video preview no video icon 2026-05-07 13:03:22 -07:00
Fedor Indutny
659fb39d28
Voice note recorder improvements 2026-05-07 11:04:16 -07:00
Jamie
9ba7277c72
Init AxoConfirmDialog and replace ConfirmationDialog 2026-05-07 09:52:50 -07:00
Fedor Indutny
d5f8c1a71a
Show live preview of voice note during recording 2026-05-07 08:55:54 -07:00
Fedor Indutny
54b5772536
Fix cursor in emoji blot 2026-05-06 15:12:43 -07:00
andrew-signal
be3c6f307d
Bump to libsignal-client v0.93.2
Co-authored-by: Marc <marc@signal.org>
Co-authored-by: Scott Nonnenberg <scott@signal.org>
2026-05-07 07:44:42 +10:00
Fedor Indutny
d33119891e
Fix check-min-os-version 2026-05-06 13:07:07 -07:00
Fedor Indutny
2f2f2aab37
Name CI jobs for easier configuration 2026-05-06 11:58:44 -07:00
trevor-signal
0c907cdc8a
Don't follow disallowed redirects in link previews 2026-05-06 11:34:11 -07:00
trevor-signal
d2b13b8cbd
Make path redaction more resilient 2026-05-06 14:00:30 -04:00
ayumi-signal
432c2ae47a
Show raised hand order in group calls 2026-05-06 10:38:59 -07:00
Jamie
d7ee96cc04
Axo improvements and documentation 2026-05-06 10:37:35 -07:00
trevor-signal
2ed50fa045
Fix image editing in MediaEditor 2026-05-06 09:37:19 -04:00
Jamie
47ea47a914
Fix recent emoji list 2026-05-05 15:44:37 -07:00
Fedor Indutny
77d48f5565
Fix AudioWorklet reuse 2026-05-05 14:12:56 -07:00
trevor-signal
97106e0d6e
Add confirmation dialog when linking to new account 2026-05-05 15:57:18 -04:00
Scott Nonnenberg
de6a50a972
sendCallUpdate: Remove inner queueJob call 2026-05-06 04:36:40 +10:00
Fedor Indutny
8335c1263e
Make VBR quality configurable 2026-05-04 16:40:51 -07:00
Jamie
592e1b4476
Init new emoji data/api 2026-05-04 16:14:53 -07:00
Scott Nonnenberg
71fe87c611
groupSendEndorsements: Ensure that group updates happen on queue
Co-authored-by: Jamie <113370520+jamiebuilds-signal@users.noreply.github.com>
2026-05-05 08:45:10 +10:00
Fedor Indutny
d6aad9d6c8
Fix test-node not finding all tests 2026-05-04 15:40:22 -07:00
Jim Gustafson
836eb19aef
Update to RingRTC v2.68.1 2026-05-04 14:08:36 -07:00
Fedor Indutny
52cf12170b
Revert "Add status trigger to backport workflow" 2026-05-04 13:21:55 -07:00
trevor-signal
328dc05de3
Improvements to local backup UI 2026-05-04 16:01:51 -04:00
Scott Nonnenberg
a8722716c1
Delete All Data: Delete account when device is standalone
Co-authored-by: ayumi-signal <143036029+ayumi-signal@users.noreply.github.com>
2026-05-05 05:16:48 +10:00
Fedor Indutny
71a3e5788b
Add status trigger to backport workflow 2026-05-04 11:34:39 -07:00
Fedor Indutny
d707a220cb
Improve auto-download logic 2026-05-04 11:03:22 -07:00
Fedor Indutny
9b9dd9f867
Upgrade electron to 41.5.0 2026-05-04 09:59:27 -07:00
ayumi-signal
a90dca4e81
Fix localization of badge dialog text 2026-05-04 08:57:45 -07:00
trevor-signal
321a2feed6
Bump electron to 41.4.0 2026-05-02 09:52:15 +10:00
Fedor Indutny
93e3cd1311
Remove await from audio worklet 2026-04-30 16:30:26 -07:00
Jamie
674471e633
Fix typescript errors 2026-04-30 14:49:02 -07:00
Jamie
f799036e39
Init AxoTextField 2026-04-30 13:16:50 -07:00
Jamie
43f6b17c66
Migrate to react-jsx 2026-04-30 11:41:57 -07:00
Scott Nonnenberg
120e359768
Preferences/Privacy: Show blocked users and groups 2026-05-01 03:18:02 +10:00
ayumi-signal
2dea157233
Linux: Auto migrate safeStorage backend between kwallet5 and kwallet6 2026-04-30 10:15:17 -07:00
ayumi-signal
04d9a109b2
Add group call participant menu to participant grid tiles 2026-04-30 09:56:12 -07:00
ayumi-signal
7eaad06b30
Fix call remote mute receive when in pip 2026-04-30 11:09:24 -04:00
Fedor Indutny
54c436c48b
Recompile audio worklet code for size/speed 2026-04-29 22:58:24 -07:00
ayumi-signal
f71f793411
Fix donate PayPal button style 2026-04-29 14:55:09 -07:00
trevor-signal
7745835143
Updates to release note details pane 2026-04-29 13:41:05 -04:00
Fedor Indutny
3ab39b61b7
Use AudioWorklet for encoding voice notes 2026-04-29 09:50:58 -07:00
trevor-signal
1902d88de9
Hide member labels for conversations in message request state 2026-04-29 11:56:44 -04:00
trevor-signal
1fa5a98e44
Release note channel UI updates 2026-04-28 22:04:18 -04:00
Fedor Indutny
6b3bb33386
Fix composition area overflow 2026-04-28 17:05:15 -07:00
Scott Nonnenberg
781a60cc65
VideoSupport: Remove black bars with minimal DOM updates 2026-04-28 10:14:06 -04:00
Scott Nonnenberg
4b79efa033
RelinkDialog: Show something different when primary device 2026-04-28 09:54:53 -04:00
Scott Nonnenberg
d791bb880c
maybeForwardMessages: Compute and queue universal timer before sends 2026-04-28 07:27:08 +10:00
trevor-signal
ec4a696d59
New safety tips dialog 2026-04-27 16:13:27 -04:00
trevor-signal
37e50ea8ec
Update message request action UI and notification 2026-04-27 16:12:38 -04:00
ayumi-signal
3409302ee3
Group call participant menu 2026-04-27 12:40:15 -07:00
trevor-signal
a8b620b23b
Avoid attempting incremental playback on undownloadable attachments 2026-04-27 14:01:27 -04:00
Scott Nonnenberg
dcc64d35cb
Fix width of too-narrow chat color picker 2026-04-27 09:05:52 -07:00
Scott Nonnenberg
bd4bb8ddff
Don't allow attachment backfill when primary device 2026-04-27 08:59:47 -07:00
Scott Nonnenberg
4b6be6f27d
TimePicker: Fix check for empty spacers 2026-04-27 08:55:22 -07:00
Fedor Indutny
9c24615058 v8.11.0-alpha.1 2026-04-26 10:49:01 -07:00
Fedor Indutny
35d8fe53d7 Update DNS fallback 2026-04-26 10:49:01 -07:00
Fedor Indutny
8606182c21 Update strings 2026-04-26 10:49:00 -07:00
Fedor Indutny
db01cc2a64 Release notes for 8.9 2026-04-26 10:49:00 -07:00
Jamie
f0b7f604f1
Fix emoji locales loading 2026-04-22 09:35:25 +10:00
Fedor Indutny
aa19f771b5
Update electron to 41.2.2 2026-04-21 11:27:19 -07:00
ayumi-signal
6dc2420f81
Fix donation receipt saving 2026-04-21 11:13:15 -07:00
Fedor Indutny
754c3cca58
Do less in start.preload.ts 2026-04-21 11:06:32 -07:00
Fedor Indutny
4fcfc2d754
Fix White Large Square not showing as emoji 2026-04-21 10:45:56 -07:00
Scott Nonnenberg
6f1915c424
CollapseSet: Include 'verified-change' messages 2026-04-21 10:04:31 -07:00
trevor-signal
5a592f95c7
Updates to message request styles 2026-04-21 09:59:56 -07:00
Fedor Indutny
281de1b6ca
Bump rolldown to rc.16 2026-04-21 08:56:05 -07:00
Fedor Indutny
593f73c51a
Show internal toast on high heap size 2026-04-20 17:24:57 -07:00
Fedor Indutny
d3892510a9
Drop END_SESSION receive support 2026-04-21 09:36:00 +10:00
Fedor Indutny
ccf899f70d
Relax document requirements for media gallery 2026-04-20 16:01:51 -04:00
Miriam Zimmerman
5f6edbdaae
calling: verify audio device persistence 2026-04-20 13:40:08 -04:00
Fedor Indutny
4b2d27dcb0
Ignore additional crash report type 2026-04-20 13:04:14 -04:00
Scott Nonnenberg
f4cdf08bbc
A few updates for the standalone registration flow 2026-04-21 03:00:57 +10:00
Fedor Indutny
0122ae3c9a
Handle UploadTooLarge error code 2026-04-20 12:34:20 -04:00
trevor-signal
bb9bb142eb
Track verified group name hash 2026-04-17 13:20:36 -07:00
Scott Nonnenberg
dd19ab4777
Preferences: Allow more options to be changed 2026-04-18 01:39:29 +10:00
Fedor Indutny
40a9c9387b
Fix debugging in Dev Tools 2026-04-17 08:10:08 -07:00
Jamie
92d6c38d07
Init AxoTheme 2026-04-16 16:51:18 -07:00
adel-signal
3be6b65f1a
Update to RingRTC v2.68.0 2026-04-16 15:19:23 -07:00
Fedor Indutny
2a3f51e250
Use chat.getUploadForm from libsignal 2026-04-16 13:20:20 -07:00
Fedor Indutny
88ec0a7b88
Cleanup patches and html preloads 2026-04-16 12:48:09 -07:00
Fedor Indutny
5fe6a8212f
Remove loading screen 2026-04-16 14:37:43 -04:00
ayumi-signal
8433e9a33d
Update electron to 41.2.1 2026-04-16 11:34:00 -07:00
Fedor Indutny
d680b7c5e2
Use rolldown for WebAudioRecorderMp3 2026-04-16 10:42:49 -07:00
Fedor Indutny
6050b239eb
Make components .dom.tsx only 2026-04-16 10:39:52 -07:00
ayumi-signal
530973ad09
Update wait-on to 9.0.5 2026-04-16 10:24:24 -07:00
trevor-signal
1b1f33d505
Updated conversation hero UI & profile name warning 2026-04-16 12:51:33 -04:00
andrew-signal
8a6b993825
Bump libsignal to 0.92.2. 2026-04-16 12:11:52 -04:00
Fedor Indutny
b0d49f77d2
Fix contenteditable="false" 2026-04-15 13:46:08 -07:00
Fedor Indutny
862162b8fa
Make prepareFileUrl synchronous 2026-04-15 13:45:53 -07:00
ayumi-signal
272465e1b2 v8.10.0-alpha.1 2026-04-15 13:28:44 -07:00
ayumi-signal
e605a2ff14 Update DNS fallback 2026-04-15 13:28:43 -07:00
ayumi-signal
e11519ec7e Update strings 2026-04-15 13:28:43 -07:00
ayumi-signal
56bf08738f Release notes for 8.8 2026-04-15 13:28:43 -07:00
ayumi-signal
4a22305de2
Update unused strings script to consider scripts 2026-04-15 12:40:42 -07:00
Jamie
817337129b
Fix emoji sort order in emoji picker 2026-04-15 10:47:13 -07:00
ayumi-signal
bd81cf103a
Fix calls tab call button showing for terminated groups 2026-04-15 09:35:28 -04:00
andrew-signal
7bd835561a
Bump libsignal to 0.92.1
Co-authored-by: Fedor Indutny <indutny@signal.org>
2026-04-14 17:59:48 -07:00
Fedor Indutny
bb07abb8b1
Remove emoji-datasource-apple 2026-04-14 16:39:11 -07:00
Scott Nonnenberg
09b7cebb6b
Clean up sync message sends
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
2026-04-15 07:43:40 +10:00
ayumi-signal
f265acc99a
Update copy for outgoing view once media toast 2026-04-14 10:01:59 -07:00
marc-signal
26905a67ab
Add option to run mocktests in background 2026-04-14 09:50:18 -07:00
ayumi-signal
e48c948baf
Save serverGuid for call messages from non-accepted chats 2026-04-13 15:09:33 -07:00
Jamie
1d53ccfee1
Setup knip 2026-04-13 12:50:00 -07:00
trevor-signal
a28d941ee9
Improve disabling of drag regions
Co-authored-by: Jamie Kyle <jamie@signal.org>
2026-04-13 14:39:36 -04:00
Fedor Indutny
400fd7ded1
Fix message rendering in RTL 2026-04-13 09:51:09 -07:00
trevor-signal
d281b60ee7
Fix fonts in storybook 2026-04-13 08:40:06 -07:00
Scott Nonnenberg
cb2b0708fb
A few improvements for the save attachment workflow 2026-04-10 16:54:54 -04:00
ayumi-signal
eedbe099b4
Improve file handler check to add path separator 2026-04-10 15:46:26 -04:00
trevor-signal
77455b5217
Tighten itemStorage types with default values
Co-authored-by: Jamie <113370520+jamiebuilds-signal@users.noreply.github.com>
2026-04-10 09:01:23 -04:00
Scott Nonnenberg
bba214676b
GroupMemberLabelEditor: No confirm discard when missing permissions 2026-04-10 09:20:47 +10:00
trevor-signal
f0cc24a2df
Add monospace font for recovery key viewing 2026-04-09 14:21:46 -07:00
trevor-signal
2e3a80b556
Simplify file referencing when deleting quoted thumbnail 2026-04-09 16:34:52 -04:00
ayumi-signal
2bdfc4979e
Always allow reporting chats as spam 2026-04-10 02:30:14 +10:00
Jamie Kyle
60a1e12545 v8.9.0-alpha.1 2026-04-08 15:25:07 -07:00
Jamie Kyle
453d56986e Update DNS fallback 2026-04-08 15:25:07 -07:00
Jamie Kyle
2979366c2e Update strings 2026-04-08 15:25:06 -07:00
Jamie Kyle
b3c84eb21c Update release notes 2026-04-08 15:25:06 -07:00
Jamie
d0bf922b6d
Update CollapseSet button style and ARIA structure 2026-04-09 08:06:42 +10:00
Fedor Indutny
91e0f526da
Introduce AssetService 2026-04-08 11:14:04 -07:00
trevor-signal
662e10831d
Simplify sync message author check 2026-04-08 14:13:29 -04:00
Fedor Indutny
977712cbe4
Make sure manifest numbers are monotonic 2026-04-08 08:59:13 -04:00
Fedor Indutny
7d7c1d0249
Use protopiler in sticker creator 2026-04-08 00:56:05 -07:00
Fedor Indutny
159712ee24
Fix schema of whoami response 2026-04-07 11:21:01 -07:00
Fedor Indutny
ed14685c58
Remove urlpattern-polyfill 2026-04-07 09:42:45 -07:00
Scott Nonnenberg
2bea0fdf6d
Input: Truncate too-long pasted text instead of dropping entirely 2026-04-08 02:35:58 +10:00
Scott Nonnenberg
0605a1afb8
Prevent forward of at-mentions, don't render in 1:1 conversations 2026-04-08 02:17:34 +10:00
Scott Nonnenberg
e68b9d81a6
Update Contributing.md 2026-04-08 01:33:55 +10:00
Jamie
dc56d2656d
Add reason strings to noop actions 2026-04-07 09:05:40 -04:00
trevor-signal
230af12965
Improve resiliency of backup folder deletion 2026-04-06 14:05:47 -07:00
trevor-signal
8bfd6a7475
Improve broken image behavior in ImageGrid 2026-04-06 12:31:27 -07:00
Fedor Indutny
f644c06474
Use code-splitting across more bundles 2026-04-06 12:24:48 -07:00
Fedor Indutny
11d5256a1c
Fix screensharing button click handler 2026-04-06 11:49:13 -07:00
Fedor Indutny
367c745d4c
Faster Windows CI 2026-04-07 04:11:44 +10:00
Scott Nonnenberg
e18638325b
Fix race condition in ConversationController tests 2026-04-03 17:38:43 -07:00
Fedor Indutny
9fe65f3d3b
Run username resolution through grpc in mocks 2026-04-03 17:16:48 -07:00
Fedor Indutny
229ac20549
gRPC mock tests 2026-04-03 16:29:20 -07:00
Scott Nonnenberg
0dcdbd9f23
Composer: Remove unneeded messageCompositionId field 2026-04-04 08:49:08 +10:00
Jamie
38799c5db7
Upgrade dependencies 2026-04-03 14:26:33 -07:00
trevor-signal
70608fbc41
Fix sizing of recovery key textarea 2026-04-03 13:22:36 -07:00
trevor-signal
0ab2412202
Cap number of incremental macs sent in a message 2026-04-03 15:28:27 -04:00
trevor-signal
54fde1dc96
Improve normalization of profile names 2026-04-03 13:36:27 -04:00
ayumi-signal
d454fe0fef
Mock test for group terminate 2026-04-03 11:17:49 -04:00
Jamie
6cbd3b9334
Enable more oxlint typescript rules 2026-04-02 16:30:34 -07:00
trevor-signal
3d624fc6d0
Fix recovery key string 2026-04-03 07:21:20 +10:00
Fedor Indutny
af73cf5094
A bit faster pnpm install in CI 2026-04-02 14:19:10 -07:00
Fedor Indutny
0dbadcbbbb
Update Electron to 41.1.1 2026-04-02 14:12:03 -07:00
Jamie
f3595e0784
Migrate scripts to ESM and ts-check 2026-04-02 13:20:15 -07:00
Fedor Indutny
5e683b0c1a
Update to mock-server@19 2026-04-02 11:52:30 -07:00
Fedor Indutny
a45114f8ad
Add pnpm script for no delay releases 2026-04-02 11:29:32 -07:00
Fedor Indutny
83764b81b2
Fix sticker creator 2026-04-03 03:04:44 +10:00
trevor-signal
e88bf72c73
Fix local backup backupId iv and counter mechanism 2026-04-02 09:41:37 -07:00
Scott Nonnenberg
0cecca0e0b v8.8.0-alpha.1 2026-04-01 16:43:36 -07:00
Scott Nonnenberg
ab52c4cd27 Update DNS fallback 2026-04-01 16:43:35 -07:00
Scott Nonnenberg
0d696b15ac Update strings 2026-04-01 16:43:35 -07:00
Scott Nonnenberg
1a9f7064bf Release notes for 8.6 2026-04-01 16:43:35 -07:00
Scott Nonnenberg
41bff2f5b9
Update libsignal to 0.91.0 2026-04-02 09:24:46 +10:00
dependabot[bot]
de0d430b72
Bump actions/checkout from 5.0.0 to 6.0.2
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Scott Nonnenberg <scott@signal.org>
2026-04-02 04:45:55 +10:00
dependabot[bot]
9ff4e75b22
Bump actions/cache from 4.3.0 to 5.0.4
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 04:35:49 +10:00
trevor-signal
f79e3ede2b
Add separate remote config flag to control max video size 2026-04-01 14:01:13 -04:00
dependabot[bot]
774522cfc9
Bump actions/setup-node from 6.0.0 to 6.3.0
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 03:29:15 +10:00
dependabot[bot]
9c68f2a747
Bump actions/upload-artifact from 4.6.2 to 7.0.0
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 03:26:21 +10:00
Scott Nonnenberg
ceca70301d
Fix danger/pnpm-lock.yaml 2026-04-02 03:12:31 +10:00
dependabot[bot]
8bd2caccf9
Bump js-yaml from 4.1.0 to 4.1.1 in /danger
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 02:59:11 +10:00
Jordan Rose
c11d503240
Provide full IdentityKeyPair to libsignal 2026-04-02 02:25:14 +10:00
ayumi-signal
9e5bd39b3f
Fix DoE received in terminated groups to match UI behavior 2026-03-31 17:57:35 -07:00
Scott Nonnenberg
245c98b875
CollapseSet: Size limit, some new message types added/excluded 2026-03-31 17:56:21 -07:00
ayumi-signal
9d26bd5ed2
Fix disappearing messages select in direct chat ConversationDetails 2026-03-31 15:30:19 -07:00
ayumi-signal
3229859157
Fix sending receipts to terminated groups 2026-03-31 15:30:04 -07:00
Fedor Indutny
83b7305258
Fix RTL rendering of delete for everyone message 2026-03-31 13:19:49 -07:00
Scott Nonnenberg
64b2247f1d
Timeline: Include all item types in Select Mode 2026-04-01 05:26:54 +10:00
Fedor Indutny
0f3f0cb0fb
Move one more dependency cleanup file list 2026-03-31 14:22:25 -04:00
trevor-signal
236470da22
Improved orphaned attachment debugging 2026-04-01 02:56:26 +10:00
trevor-signal
31ae6a1e44
Update local backup OS auth strings 2026-03-31 12:54:13 -04:00
ayumi-signal
77367df528
Don't use send endorsements when fetching profiles in ended groups 2026-03-31 09:53:47 -07:00
trevor-signal
5dbdfaf5c5
Enable local backups after unlink 2026-03-31 09:26:07 -04:00
Fedor Indutny
99da687995
Further ASAR size reduction 2026-03-30 18:53:52 -07:00
Jamie
3d04a8ba0b
Upgrade danger setup and add tailwind deps rule 2026-03-30 17:26:23 -07:00
Fedor Indutny
f665b35478
Remove unused patch 2026-03-30 17:13:31 -07:00
Fedor Indutny
fc12ea015a
Use *-proxy-agent modules directly 2026-03-30 16:59:57 -07:00
Fedor Indutny
d8ef296bc3
Update got to 14.6.6 2026-03-30 14:31:58 -07:00
Fedor Indutny
a10783f4e0
Enable import/extensions lint rule 2026-03-30 14:24:45 -07:00
Jamie
2dd05d0221
Upgrade Tailwind packages to same version 2026-03-31 07:16:04 +10:00
ayumi-signal
bb284b6829
Update strings for group terminate 2026-03-30 16:20:22 -04:00
Fedor Indutny
bb111a3107
Bundle everything with rolldown 2026-03-30 11:54:59 -07:00
ayumi-signal
e158261fea
Fix actions available on ended groups 2026-03-28 10:51:26 +10:00
ayumi-signal
b7ca8f278a
Prevent poll voting in ended groups 2026-03-27 15:13:09 -07:00
Jamie
caa10d02c3
Switch from eslint to oxlint 2026-03-27 13:40:46 -07:00
Scott Nonnenberg
224bb811e1
CollapseSet: Improve animation
Co-authored-by: Jamie Kyle <jamie@signal.org>
2026-03-27 16:34:07 -04:00
trevor-signal
d8df1e2869
Exclude invalid source-less incoming messages 2026-03-27 16:30:15 -04:00
trevor-signal
cb8886ebd9
Add recovery key changed modal 2026-03-27 15:29:13 -04:00
Fedor Indutny
f1f2055058
Update libsignal to 0.90.0 2026-03-27 12:13:14 -07:00
trevor-signal
35328c39ed
Update strings for recovery key 2026-03-27 15:03:59 -04:00
Fedor Indutny
d4b1c1c9f7
Update electron to 40.8.5 2026-03-27 15:03:22 -04:00
trevor-signal
346876171d
Improve performance of test-electron 2026-03-27 11:40:59 -07:00
Fedor Indutny
a048f83dbc
Remove use of __dirname from main process 2026-03-27 10:55:37 -07:00
trevor-signal
70f111e868
Fix some flaky test-electron tests 2026-03-28 03:42:53 +10:00
ayumi-signal
f975d864c7
Linux: Fix development auth prompts 2026-03-27 13:19:30 -04:00
trevor-signal
6d072b4546
Improve processing of mentions in direct conversations 2026-03-26 15:34:42 -04:00
Fedor Indutny
df99252d82
Fix some import cycles 2026-03-26 10:25:51 -07:00
ayumi-signal
e7544a5565
Init group terminate 2026-03-25 17:00:48 -07:00
Fedor Indutny
61095cc0a1
Update protopiler to 3.2.4 2026-03-25 15:46:30 -07:00
ayumi-signal
e8efc3c660 v8.7.0-alpha.1 2026-03-25 14:36:35 -07:00
ayumi-signal
dad6e5553d Update DNS fallback 2026-03-25 14:36:35 -07:00
ayumi-signal
a8bb86bb39 Update strings 2026-03-25 14:36:34 -07:00
ayumi-signal
68f41e7072 Release notes for 8.5 2026-03-25 14:36:34 -07:00
trevor-signal
3ff9acdcfc
Strip visual attachment filenames for local device 2026-03-25 14:16:21 -04:00
Fedor Indutny
141d5be379
Add profileName to left pane search terms 2026-03-25 10:13:09 -07:00
Fedor Indutny
0e1e4d42ec
Simplify TaskWithTimeout further 2026-03-25 10:05:10 -07:00
andrew-signal
b2b9f78993
Bump to libsignal v0.89.2 2026-03-24 15:59:24 -07:00
trevor-signal
3a25597873
Allow incremental playback for attachments with plaintextHash 2026-03-25 07:23:02 +10:00
Scott Nonnenberg
c353d41794
Collapsing Items: A few improvements 2026-03-25 07:00:02 +10:00
Fedor Indutny
97cf9a90fb
Serialize sql args/results 2026-03-24 12:13:39 -07:00
Fedor Indutny
c6e9f5668a
Simplify esbuild script 2026-03-24 10:02:13 -07:00
Fedor Indutny
e42c58667e
Update protopiler to 3.2.3 2026-03-24 12:14:06 -04:00
Fedor Indutny
b8cca2c49c
Simplify TaskWithTimeout
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
2026-03-24 09:08:36 -07:00
trevor-signal
c050a0c8c2
Mark accepted or outgoing callHistory as seen 2026-03-25 01:15:22 +10:00
trevor-signal
8ad63966ae
Update local backups availability during rollout 2026-03-24 07:47:38 +10:00
Fedor Indutny
f88b6fc293
Update protopiler to 3.2.2 (#12533) 2026-03-23 14:10:05 -07:00
trevor-signal
1ead5dc14a
Handle unregistered actions when prompting for OS auth 2026-03-23 12:34:27 -07:00
Scott Nonnenberg
3cf38b1b40
Collapse items into multi-day sets, handling start/end incomplete days 2026-03-21 07:15:42 +10:00
Scott Nonnenberg
27ad6f3294
Collapse already-seen sets of timeline items 2026-03-21 02:58:24 +10:00
Fedor Indutny
3f34ef9693
Show update progress bar on force update with QR 2026-03-20 09:53:38 -07:00
Fedor Indutny
c863dfa66b
Ignore expireTimerVersion=0 messages 2026-03-20 12:40:04 -04:00
Fedor Indutny
100db18701
Fix text story background color 2026-03-19 16:54:17 -07:00
Fedor Indutny
1d884929ea
Add background to .dmg file 2026-03-19 10:32:38 -07:00
adel-signal
8b510c3b30
calling: add internal preferences for DRED, bitrate, VP9, sfu url 2026-03-19 09:46:33 -07:00
andrew-signal
b624e48cf1
Bump to libsignal v0.89.1 2026-03-19 12:12:17 -04:00
trevor-signal
284c10973d
Include prefix for PNI serviceIds on export 2026-03-19 11:50:11 -04:00
Fedor Indutny
d34506f400
Check imported dlls in CI
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
2026-03-19 08:37:46 -07:00
trevor-signal
a8118faf08 v8.6.0-alpha.1 2026-03-19 09:41:27 -04:00
3248 changed files with 126000 additions and 174870 deletions

View File

@ -1,20 +1,24 @@
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
module.exports = {
/** @type {import("@babel/core").TransformOptions} */
const config = {
presets: ['@babel/preset-react', '@babel/preset-typescript'],
// Detects the type of file being babel'd (either esmodule or commonjs)
sourceType: 'unambiguous',
plugins: [
'lodash',
'@babel/plugin-transform-typescript',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-proposal-nullish-coalescing-operator',
// This plugin converts commonjs to esmodules which is required for
// importing commonjs modules from esmodules in storybook. As a part of
// converting to TypeScript we should use esmodules and can eventually
// remove this plugin
process.env.SIGNAL_ENV === 'storybook' && '@babel/transform-runtime',
].filter(Boolean),
process.env.SIGNAL_ENV === 'storybook' &&
import.meta.resolve('@babel/plugin-transform-runtime'),
].filter(plugin => {
return typeof plugin === 'string';
}),
};
export default config;

View File

@ -1,42 +0,0 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
const rule = require('./enforce-tw');
const RuleTester = require('eslint').RuleTester;
const message = 'Tailwind classes must be wrapped with tw()';
// avoid triggering mocha's global leak detection
require('@typescript-eslint/parser');
const ruleTester = new RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
});
ruleTester.run('enforce-tw', rule, {
valid: [
{ code: `classNames("foo")` },
{ code: `<div className="foo"/>` },
{ code: `tw("flex")` },
],
invalid: [
{ code: `classNames("flex")`, errors: [{ message }] },
{ code: `<div className="flex"/>`, errors: [{ message }] },
{ code: `<div className={"flex"}/>`, errors: [{ message }] },
{ code: `classNames("foo", "flex")`, errors: [{ message }] },
{ code: `classNames(cond ? "foo" : "flex")`, errors: [{ message }] },
{ code: `classNames(cond ? "flex" : "foo")`, errors: [{ message }] },
{ code: `classNames(cond && "flex")`, errors: [{ message }] },
{ code: `classNames(cond || "flex")`, errors: [{ message }] },
{ code: `classNames(cond ?? "flex")`, errors: [{ message }] },
{ code: `classNames("foo" + "flex")`, errors: [{ message }] },
{ code: `classNames("flex" + "foo")`, errors: [{ message }] },
],
});

View File

@ -1,134 +0,0 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
const rule = require('./file-suffix.js');
const RuleTester = require('eslint').RuleTester;
// avoid triggering mocha's global leak detection
require('@typescript-eslint/parser');
const ruleTester = new RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
});
ruleTester.run('file-suffix', rule, {
valid: [
// Allowed references
...[
['std', '', ['std']],
['dom', 'window.addEventListener();', ['std', 'dom']],
['node', 'require("node:fs");', ['std', 'node']],
[
'preload',
'import { ipcRenderer } from "electron";',
['std', 'node', 'preload'],
],
[
'main',
'import { autoUpdater } from "electron";',
['std', 'node', 'main'],
],
]
.map(([fileSuffix, requiredLine, depSuffixes]) => {
return depSuffixes.map(depSuffix => {
return {
name: `importing ${depSuffix} from ${fileSuffix}`,
filename: `a.${fileSuffix}.ts`,
code: `
import { x } from './b.${depSuffix}.js';
${requiredLine}
`,
globals: {
window: 'writable',
require: 'readable',
},
};
});
})
.flat(),
{
name: 'type import should have no effect',
filename: 'a.std.ts',
code: `import type { ReadonlyDeep } from './b.dom.js'`,
},
],
invalid: [
// Disallowed references
...[
['std', ['dom', 'node', 'preload', 'main']],
['dom', ['node', 'preload', 'main']],
['node', ['preload', 'main']],
['preload', ['main']],
['main', ['dom', 'preload']],
]
.map(([fileSuffix, depSuffixes]) => {
return depSuffixes.map(depSuffix => {
return {
name: `importing ${depSuffix} from ${fileSuffix}`,
filename: `a.${fileSuffix}.ts`,
code: `import { x } from './b.${depSuffix}.js'`,
errors: [
{
message: `Invalid suffix ${fileSuffix}, expected: ${depSuffix}`,
type: 'Program',
},
],
};
});
})
.flat(),
...['dom', 'node', 'preload', 'main'].map(suffix => {
return {
name: `no ${suffix} imports`,
filename: `a.${suffix}.ts`,
code: '',
errors: [
{
message: `Invalid suffix ${suffix}, expected: std`,
type: 'Program',
},
],
};
}),
// Invalid imports
{
name: 'preload in main',
filename: 'a.main.ts',
code: `
import { autoUpdater } from 'electron';
import './b.preload.js';
`,
errors: [
{
message: 'Invalid import/reference for suffix: main',
type: 'ImportDeclaration',
},
],
},
{
name: 'main in preload',
filename: 'a.preload.ts',
code: `
import { ipcRenderer } from 'electron';
import './b.main.js';
`,
errors: [
{
message: 'Invalid suffix preload, expected: main',
type: 'Program',
},
{
message: 'Invalid import/reference for suffix: main',
type: 'ImportSpecifier',
},
],
},
],
});

View File

@ -1,58 +0,0 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
function isReadOnlyDeep(node, scope) {
if (node.type !== 'TSTypeReference') {
return false;
}
let reference = scope.references.find(reference => {
return reference.identifier === node.typeName;
});
let variable = reference.resolved;
if (variable == null) {
return false;
}
let defs = variable.defs;
if (defs.length !== 1) {
return false;
}
let [def] = defs;
return (
def.type === 'ImportBinding' &&
def.parent.type === 'ImportDeclaration' &&
def.parent.source.type === 'Literal' &&
def.parent.source.value === 'type-fest'
);
}
/** @type {import("eslint").Rule.RuleModule} */
module.exports = {
meta: {
type: 'problem',
hasSuggestions: false,
fixable: false,
schema: [],
},
create(context) {
return {
TSTypeAliasDeclaration(node) {
let scope = context.getScope(node);
if (isReadOnlyDeep(node.typeAnnotation, scope)) {
return;
}
context.report({
node: node.id,
message:
'Type aliases must be wrapped with ReadonlyDeep from type-fest',
});
},
};
},
};

View File

@ -1,79 +0,0 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
const rule = require('./type-alias-readonlydeep');
const RuleTester = require('eslint').RuleTester;
// avoid triggering mocha's global leak detection
require('@typescript-eslint/parser');
const ruleTester = new RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
});
ruleTester.run('type-alias-readonlydeep', rule, {
valid: [
{
code: `import type { ReadonlyDeep } from "type-fest"; type Foo = ReadonlyDeep<{}>`,
},
{
code: `import { ReadonlyDeep } from "type-fest"; type Foo = ReadonlyDeep<{}>`,
},
],
invalid: [
{
code: `type Foo = {}`,
errors: [
{
message:
'Type aliases must be wrapped with ReadonlyDeep from type-fest',
type: 'Identifier',
},
],
},
{
code: `type Foo = Bar<{}>`,
errors: [
{
message:
'Type aliases must be wrapped with ReadonlyDeep from type-fest',
type: 'Identifier',
},
],
},
{
code: `type Foo = ReadonlyDeep<{}>`,
errors: [
{
message:
'Type aliases must be wrapped with ReadonlyDeep from type-fest',
type: 'Identifier',
},
],
},
{
code: `interface ReadonlyDeep<T> {}; type Foo = ReadonlyDeep<{}>`,
errors: [
{
message:
'Type aliases must be wrapped with ReadonlyDeep from type-fest',
type: 'Identifier',
},
],
},
{
code: `import type { ReadonlyDeep } from "foo"; type Foo = ReadonlyDeep<{}>`,
errors: [
{
message:
'Type aliases must be wrapped with ReadonlyDeep from type-fest',
type: 'Identifier',
},
],
},
],
});

View File

@ -1,37 +0,0 @@
components/**
coverage/**
dist/**
release/**
# Github workflows
.github/**
# Generated files
js/curve/*
js/components.js
js/util_worker.js
libtextsecure/components.js
libtextsecure/test/test.js
test/test.js
ts/protobuf/compiled.std.d.ts
storybook-static/**
build/ICUMessageParams.d.ts
# Third-party files
js/Mp3LameEncoder.min.js
js/WebAudioRecorderMp3.js
js/calling-tools/**
# TypeScript generated files
build/**/*.js
app/**/*.js
ts/**/*.js
.eslintrc.js
webpack.config.ts
preload.bundle.*
preload.wrapper.*
bundles/**
# Sticker Creator has its own eslint config
sticker-creator/**

View File

@ -1,485 +0,0 @@
// Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// For reference: https://github.com/airbnb/javascript
const rules = {
'comma-dangle': [
'error',
{
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'always-multiline',
functions: 'never',
},
],
// No omitting braces, keep on the same line
'brace-style': ['error', '1tbs', { allowSingleLine: false }],
curly: ['error', 'all'],
// Immer support
'no-param-reassign': [
'error',
{
props: true,
ignorePropertyModificationsForRegex: ['^draft'],
ignorePropertyModificationsFor: ['acc', 'ctx', 'context'],
},
],
// Always use === and !== except when directly comparing to null
// (which only will equal null or undefined)
eqeqeq: ['error', 'always', { null: 'never' }],
// prevents us from accidentally checking in exclusive tests (`.only`):
'mocha/no-exclusive-tests': 'error',
// encourage consistent use of `async` / `await` instead of `then`
'more/no-then': 'error',
// it helps readability to put public API at top,
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
// useful for unused or internal fields
'no-underscore-dangle': 'off',
// Temp: We have because TypeScript's `allowUnreachableCode` option is on.
'no-unreachable': 'error',
// though we have a logger, we still remap console to log to disk
'no-console': 'error',
// consistently place operators at end of line except ternaries
'operator-linebreak': [
'error',
'after',
{ overrides: { '?': 'ignore', ':': 'ignore' } },
],
quotes: [
'error',
'single',
{ avoidEscape: true, allowTemplateLiterals: false },
],
'no-continue': 'off',
'lines-between-class-members': 'off',
'class-methods-use-this': 'off',
// Prettier overrides:
'arrow-parens': 'off',
'function-paren-newline': 'off',
'max-len': [
'error',
{
// Prettier generally limits line length to 80 but sometimes goes over.
// The `max-len` plugin doesnt let us omit `code` so we set it to a
// high value as a buffer to let Prettier control the line length:
code: 999,
// We still want to limit comments as before:
comments: 90,
ignoreUrls: true,
},
],
'react/jsx-props-no-spreading': 'off',
// Updated to reflect future airbnb standard
// Allows for declaring defaultProps inside a class
'react/static-property-placement': ['error', 'static public field'],
// JIRA: DESKTOP-657
'react/sort-comp': 'off',
// We don't have control over the media we're sharing, so can't require
// captions.
'jsx-a11y/media-has-caption': 'off',
// We prefer named exports
'import/prefer-default-export': 'off',
'import/enforce-node-protocol-usage': ['error', 'always'],
'import/extensions': [
'error',
'ignorePackages',
{
checkTypeImports: true,
},
],
// Prefer functional components with default params
'react/require-default-props': 'off',
// Empty fragments are used in adapters between models and react views.
'react/jsx-no-useless-fragment': [
'error',
{
allowExpressions: true,
},
],
// Our code base has tons of arrow functions passed directly to components.
'react/jsx-no-bind': 'off',
// Does not support forwardRef
'react/no-unused-prop-types': 'off',
// Not useful for us as we have lots of complicated types.
'react/destructuring-assignment': 'off',
'react/function-component-definition': [
'error',
{
namedComponents: 'function-declaration',
unnamedComponents: 'arrow-function',
},
],
'react/display-name': 'error',
'react/jsx-pascal-case': ['error', { allowNamespace: true }],
// Allow returning values from promise executors for brevity.
'no-promise-executor-return': 'off',
// Redux ducks use this a lot
'default-param-last': 'off',
'jsx-a11y/label-has-associated-control': ['error', { assert: 'either' }],
'jsx-a11y/no-static-element-interactions': 'error',
'@typescript-eslint/no-non-null-assertion': ['error'],
'@typescript-eslint/no-empty-interface': ['error'],
'no-empty-function': 'off',
'@typescript-eslint/no-empty-function': 'error',
'no-restricted-syntax': [
'error',
{
selector: 'TSInterfaceDeclaration',
message:
'Prefer `type`. Interfaces are mutable and less powerful, so we prefer `type` for simplicity.',
},
// Defaults
{
selector: 'ForInStatement',
message:
'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.',
},
{
selector: 'LabeledStatement',
message:
'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.',
},
{
selector: 'WithStatement',
message:
'`with` is disallowed in strict mode because it makes code impossible to predict and optimize.',
},
],
'react-hooks/exhaustive-deps': [
'error',
{
additionalHooks: '^(useSpring|useSprings)$',
},
],
'local-rules/license-comments': 'error',
};
const typescriptRules = {
...rules,
'local-rules/file-suffix': 'error',
// Override brace style to enable typescript-specific syntax
'brace-style': 'off',
'@typescript-eslint/brace-style': [
'error',
'1tbs',
{ allowSingleLine: false },
],
'@typescript-eslint/array-type': ['error', { default: 'generic' }],
'no-restricted-imports': 'off',
'@typescript-eslint/no-restricted-imports': [
'error',
{
paths: [
{
name: 'chai',
importNames: ['expect', 'should', 'Should'],
message: 'Please use assert',
allowTypeImports: true,
},
],
},
],
// Overrides recommended by typescript-eslint
// https://github.com/typescript-eslint/typescript-eslint/releases/tag/v4.0.0
'@typescript-eslint/no-redeclare': 'error',
'@typescript-eslint/no-shadow': 'error',
'@typescript-eslint/no-useless-constructor': ['error'],
'@typescript-eslint/no-misused-promises': [
'error',
{
checksVoidReturn: false,
},
],
'@typescript-eslint/no-floating-promises': 'error',
// We allow "void promise", but new call-sites should use `drop(promise)`.
'no-void': ['error', { allowAsStatement: true }],
'no-shadow': 'off',
'no-useless-constructor': 'off',
// useful for unused parameters
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
// Upgrade from a warning
'@typescript-eslint/explicit-module-boundary-types': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
// Future: Maybe switch to never and always use `satisfies`
'@typescript-eslint/consistent-type-assertions': [
'error',
{
assertionStyle: 'as',
// Future: Maybe switch to allow-as-parameter or never
objectLiteralTypeAssertions: 'allow',
},
],
// Already enforced by TypeScript
'consistent-return': 'off',
// TODO: DESKTOP-4655
'import/no-cycle': 'off',
'import/no-restricted-paths': [
'error',
{
zones: [
{
target: ['ts/util', 'ts/types'],
from: ['ts/components/**', 'ts/axo/**/*.dom.*'],
message: 'Importing components is forbidden from ts/{util,types}',
},
],
},
],
'local-rules/enforce-array-buffer': 'error',
};
const TAILWIND_REPLACEMENTS = [
// inset
{ pattern: 'left-*', fix: 'start-*' },
{ pattern: 'right-*', fix: 'end-*' },
// margin
{ pattern: 'ml-*', fix: 'ms-*' },
{ pattern: 'mr-*', fix: 'me-*' },
// padding
{ pattern: 'pl-*', fix: 'ps-*' },
{ pattern: 'pr-*', fix: 'pe-*' },
// border
{ pattern: 'border-l-*', fix: 'border-s-*' },
{ pattern: 'border-r-*', fix: 'border-e-*' },
// border-radius
{ pattern: 'rounded-l', fix: 'rounded-s' },
{ pattern: 'rounded-r', fix: 'rounded-e' },
{ pattern: 'rounded-tl', fix: 'rounded-ss' },
{ pattern: 'rounded-tr', fix: 'rounded-se' },
{ pattern: 'rounded-bl', fix: 'rounded-es' },
{ pattern: 'rounded-br', fix: 'rounded-ee' },
{ pattern: 'rounded-l-*', fix: 'rounded-s-*' },
{ pattern: 'rounded-r-*', fix: 'rounded-e-*' },
{ pattern: 'rounded-tl-*', fix: 'rounded-ss-*' },
{ pattern: 'rounded-tr-*', fix: 'rounded-se-*' },
{ pattern: 'rounded-bl-*', fix: 'rounded-es-*' },
{ pattern: 'rounded-br-*', fix: 'rounded-ee-*' },
// text-align
{ pattern: 'text-left', fix: 'text-start' },
{ pattern: 'text-right', fix: 'text-end' },
// float
{ pattern: 'float-left', fix: 'float-start' },
{ pattern: 'float-right', fix: 'float-end' },
// clear
{ pattern: 'clear-left', fix: 'clear-start' },
{ pattern: 'clear-right', fix: 'clear-end' },
];
module.exports = {
root: true,
settings: {
react: {
version: 'detect',
},
'import/core-modules': ['electron'],
},
extends: ['airbnb-base', 'prettier'],
plugins: ['mocha', 'more', 'local-rules'],
overrides: [
{
files: [
'ts/**/*.ts',
'ts/**/*.tsx',
'app/**/*.ts',
'app/**/*.tsx',
'build/intl-linter/**/*.ts',
],
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018,
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'airbnb-typescript-prettier',
],
rules: typescriptRules,
},
{
files: [
'**/*.stories.tsx',
'ts/build/**',
'ts/test-*/**',
'build/intl-linter/**/*.ts',
],
rules: {
...typescriptRules,
'import/no-extraneous-dependencies': 'off',
'react/no-array-index-key': 'off',
},
},
{
files: ['ts/state/ducks/**/*.ts'],
rules: {
'local-rules/type-alias-readonlydeep': 'error',
},
},
{
files: ['ts/**/*_test.*.{ts,tsx}'],
rules: {
'func-names': 'off',
},
},
{
files: ['ts/**/*.tsx'],
plugins: ['better-tailwindcss'],
settings: {
'better-tailwindcss': {
entryPoint: './stylesheets/tailwind-config.css',
callees: ['tw'],
attributes: [],
variables: [],
},
},
rules: {
'local-rules/enforce-tw': 'error',
// stylistic: Enforce consistent line wrapping for tailwind classes. (recommended, autofix)
'better-tailwindcss/enforce-consistent-line-wrapping': 'off',
// stylistic: Enforce a consistent order for tailwind classes. (recommended, autofix)
'better-tailwindcss/enforce-consistent-class-order': 'error',
// stylistic: Enforce consistent variable syntax. (autofix)
'better-tailwindcss/enforce-consistent-variable-syntax': 'error',
// stylistic: Enforce consistent position of the important modifier. (autofix)
'better-tailwindcss/enforce-consistent-important-position': 'error',
// stylistic: Enforce shorthand class names. (autofix)
'better-tailwindcss/enforce-shorthand-classes': 'error',
// stylistic: Remove duplicate classes. (autofix)
'better-tailwindcss/no-duplicate-classes': 'error',
// stylistic: Remove deprecated classes. (autofix)
'better-tailwindcss/no-deprecated-classes': 'off',
// stylistic: Disallow unnecessary whitespace in tailwind classes. (autofix)
'better-tailwindcss/no-unnecessary-whitespace': 'error',
// correctness: Report classes not registered with tailwindcss. (recommended)
'better-tailwindcss/no-unregistered-classes': 'error',
// correctness: Report classes that produce conflicting styles.
'better-tailwindcss/no-conflicting-classes': 'error',
// correctness: Disallow restricted classes. (autofix)
'better-tailwindcss/no-restricted-classes': [
'error',
{
restrict: [
{
pattern: '\\[#[a-fA-F0-9]{3,8}?\\]', // ex: "text-[#fff]"
message: 'No arbitrary hex values',
},
{
pattern: '\\[rgba?\\(.*\\)\\]', // ex: "text-[rgb(255,255,255)]"
message: 'No arbitrary rgb values',
},
{
pattern: '\\[hsla?\\(.*\\)\\]', // ex: "text-[hsl(255,255,255)]"
message: 'No arbitrary hsl values',
},
{
pattern: '^.*!$', // ex: "p-4!"
message: 'No !important modifiers',
},
{
pattern: '^\\*+:.*', // ex: "*:mx-0",
message: 'No child variants',
},
...TAILWIND_REPLACEMENTS.map(item => {
const pattern = item.pattern.replace('*', '(.*)');
const fix = item.fix.replace('*', '$2');
return {
message: `Use logical property ${item.fix} instead of ${item.pattern}`,
pattern: `^(.*:)?${pattern}$`,
fix: `$1${fix}`,
};
}),
],
},
],
},
},
{
files: ['ts/axo/**/*.{ts,tsx}'],
rules: {
// Rule doesn't understand TypeScript namespaces
'no-inner-declarations': 'off',
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-redeclare': [
'error',
{
ignoreDeclarationMerge: true,
},
],
'@typescript-eslint/explicit-module-boundary-types': [
'error',
{
allowHigherOrderFunctions: false,
},
],
},
},
],
rules: {
...rules,
'import/no-unresolved': 'off',
'import/extensions': 'off',
},
reportUnusedDisableDirectives: true,
};

View File

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.AUTOMATED_GITHUB_PAT }}
repository: signalapp/Signal-Backport-Action-Private

View File

@ -1,165 +0,0 @@
# Copyright 2020 Signal Messenger, LLC
# SPDX-License-Identifier: AGPL-3.0-only
name: Benchmark
on:
push:
branches:
- development
- main
- '[0-9]+.[0-9]+.x'
pull_request:
schedule:
- cron: '0 */12 * * *'
jobs:
linux:
strategy:
matrix:
metric:
- startup
- send
- groupSend
- largeGroupSendWithBlocks
- largeGroupSend
- convoOpen
- callHistorySearch
- backup
include:
- metric: startup
script: ts/test-mock/benchmarks/startup_bench.node.js
runCount: 10
- metric: send
script: ts/test-mock/benchmarks/send_bench.node.js
runCount: 100
- metric: groupSend
script: ts/test-mock/benchmarks/group_send_bench.node.js
runCount: 100
conversationSize: 500
- metric: largeGroupSendWithBlocks
script: ts/test-mock/benchmarks/group_send_bench.node.js
runCount: 50
conversationSize: 500
groupSize: 500
contactCount: 500
blockedCount: 10
discardCount: 2
- metric: largeGroupSend
script: ts/test-mock/benchmarks/group_send_bench.node.js
runCount: 20
conversationSize: 50
groupSize: 500
contactCount: 500
discardCount: 2
- metric: convoOpen
script: ts/test-mock/benchmarks/convo_open_bench.node.js
runCount: 100
- metric: callHistorySearch
script: ts/test-mock/benchmarks/call_history_search_bench.node.js
runCount: 100
- metric: backup
script: ts/test-mock/benchmarks/backup_bench.node.js
runs-on: ubuntu-22.04-8-cores
if: ${{ github.repository == 'signalapp/Signal-Desktop-Private' && (!github.event.schedule || github.ref == 'refs/heads/main') }}
timeout-minutes: 30
steps:
- name: Get system specs
run: lsb_release -a
- name: Get other system specs
run: uname -a
- name: Clone Desktop repo
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
# - name: Setup sccache
# uses: mozilla-actions/sccache-action@054db53350805f83040bf3e6e9b8cf5a139aa7c9 # v0.0.7
# - name: Restore sccache
# uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
# id: cache-sccache
# with:
# path: ${{ env.SCCACHE_PATH }}
# key: sccache-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'patches/**') }}
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
cache-dependency-path: 'pnpm-lock.yaml'
- name: Cache .electron-gyp
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.electron-gyp
key: electron-gyp-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install xvfb and libpulse0
run: sudo apt-get install xvfb libpulse0 || (sudo apt-get update && sudo apt-get install xvfb libpulse0)
- name: Install Desktop node_modules
run: pnpm install
env:
# CC: sccache gcc
# CXX: sccache g++
# SCCACHE_GHA_ENABLED: "true"
NPM_CONFIG_LOGLEVEL: verbose
- name: Build typescript
run: pnpm run generate
- name: Bundle
run: pnpm run build:esbuild:prod
- name: Create preload cache
run: xvfb-run --auto-servernum pnpm run build:preload-cache
- name: Set MAX_CYCLES=2 on main
if: ${{ github.ref == 'refs/heads/main' }}
run: |
echo "MAX_CYCLES=2" >> "$GITHUB_ENV"
- name: Run ${{ matrix.metric }}
run: |
set -o pipefail
xvfb-run --auto-servernum node ${{ matrix.script }} | tee benchmark.log
timeout-minutes: 10
env:
NODE_ENV: production
ELECTRON_ENABLE_STACK_DUMPING: on
DEBUG: 'mock:benchmarks'
ARTIFACTS_DIR: artifacts/${{ matrix.metric }}
GROUP_SIZE: ${{ matrix.groupSize }}
CONTACT_COUNT: ${{ matrix.contactCount }}
BLOCKED_COUNT: ${{ matrix.blockedCount }}
DISCARD_COUNT: ${{ matrix.discardCount }}
RUN_COUNT: ${{ matrix.runCount }}
CONVERSATION_SIZE: ${{ matrix.conversationSize }}
- name: Upload benchmark logs on failure
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: logs
path: artifacts
- name: Clone benchmark repo
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
repository: 'signalapp/Signal-Desktop-Benchmarks-Private'
path: 'benchmark-results'
token: ${{ secrets.AUTOMATED_GITHUB_PAT }}
- name: Build benchmark repo
working-directory: benchmark-results
run: |
pnpm install
pnpm run build
- name: Publish
working-directory: benchmark-results
run: |
node ./bin/publish.js ../benchmark.log desktop.ci.performance.${{ matrix.metric }}
env:
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}

View File

@ -10,25 +10,45 @@ on:
- '[0-9]+.[0-9]+.x'
pull_request:
permissions:
contents: read
jobs:
audit:
name: Dependencies
runs-on: ubuntu-22.04-8-cores
timeout-minutes: 30
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: '.nvmrc'
- run: node --test .pnpmfile.mjs
- run: pnpm audit --audit-level=high
- run: pnpm audit signatures
- run: pnpm dedupe --check
lint:
name: Lint
runs-on: ubuntu-22.04-8-cores
timeout-minutes: 30
steps:
- run: lsb_release -a
- run: uname -a
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
cache-dependency-path: 'pnpm-lock.yaml'
- name: Cache .electron-gyp
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.electron-gyp
key: electron-gyp-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
@ -36,20 +56,11 @@ jobs:
# - name: Setup sccache
# uses: mozilla-actions/sccache-action@054db53350805f83040bf3e6e9b8cf5a139aa7c9 # v0.0.7
# - name: Restore sccache
# uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
# uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
# with:
# path: ${{ env.SCCACHE_PATH }}
# key: sccache-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'patches/**') }}
- name: Restore cached .eslintcache and tsconfig.tsbuildinfo
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
id: cache-lint
with:
path: |
.eslintcache
tsconfig.tsbuildinfo
key: lint-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'patches/**', '.eslintrc.js', '.eslint/**', 'tsconfig.json') }}
- name: Install Desktop node_modules
run: pnpm install
env:
@ -57,12 +68,27 @@ jobs:
# CXX: sccache g++
# SCCACHE_GHA_ENABLED: "true"
NPM_CONFIG_LOGLEVEL: verbose
# We rebuild in `electron:install-app-deps` that doesn't look at this
# environment variable
NPM_CONFIG_NODE_GYP: echo
- name: Install Sticker Creator node_modules
run: pnpm install
working-directory: sticker-creator
- name: Install libpulse0
run: sudo apt-get install -y libpulse0 || (sudo apt-get update && sudo apt-get install -y libpulse0)
- run: pnpm run generate
- run: pnpm run lint
- run: pnpm run build:db-schema --check
- run: pnpm run lint-prettier
- run: pnpm run lint-css
- run: pnpm run check:types
- run: pnpm run oxlint:ci
- run: pnpm run lint-deps
- run: pnpm run lint-license-comments
- run: pnpm run lint-intl
- run: pnpm run lint-knip:all --reporter github-actions
- run: pnpm run lint-knip:prod --reporter github-actions
- name: Check acknowledgments file is up to date
run: pnpm run build:acknowledgments
env:
@ -70,34 +96,26 @@ jobs:
- run: git diff --exit-code
- name: Update cached .eslintcache and tsconfig.tsbuildinfo
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
if: github.ref == 'refs/heads/main'
with:
path: |
.eslintcache
tsconfig.tsbuildinfo
key: ${{ steps.cache-lint.outputs.cache-primary-key }}
macos:
name: MacOS
needs: lint
runs-on: macos-latest
runs-on: macos-26-arm64
if: github.ref == 'refs/heads/main'
timeout-minutes: 30
steps:
- run: uname -a
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
cache-dependency-path: 'pnpm-lock.yaml'
- name: Cache .electron-gyp
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.electron-gyp
key: electron-gyp-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
@ -105,7 +123,7 @@ jobs:
# - name: Setup sccache
# uses: mozilla-actions/sccache-action@054db53350805f83040bf3e6e9b8cf5a139aa7c9 # v0.0.7
# - name: Restore sccache
# uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
# uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
# with:
# path: ${{ env.SCCACHE_PATH }}
# key: sccache-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'patches/**') }}
@ -117,9 +135,11 @@ jobs:
# CXX: sccache clang++
# SCCACHE_GHA_ENABLED: "true"
NPM_CONFIG_LOGLEVEL: verbose
- run: pnpm run generate
# We rebuild in `electron:install-app-deps` that doesn't look at this
# environment variable
NPM_CONFIG_NODE_GYP: echo
- run: pnpm run prepare-beta-build
- run: pnpm run generate
- run: pnpm run test-node
- run: pnpm run test-electron
env:
@ -138,38 +158,45 @@ jobs:
- name: Upload installer size
if: ${{ github.repository == 'signalapp/Signal-Desktop-Private' && github.ref == 'refs/heads/main' }}
run: |
node ts/scripts/publish-installer-size.node.js macos-arm64
node ts/scripts/publish-installer-size.node.js macos-x64
node ts/scripts/publish-installer-size.node.js macos-universal
node scripts/publish-installer-size.mjs macos-arm64
node scripts/publish-installer-size.mjs macos-x64
node scripts/publish-installer-size.mjs macos-universal
- run: pnpm run test-release
env:
NODE_ENV: production
- name: Upload artifacts on failure
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
path: artifacts
linux:
name: Linux
needs: lint
runs-on: ubuntu-22.04-8-cores
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
matrix:
include:
- os: ubuntu-22.04-8-cores
arch: x64
- os: ubuntu-22.04-arm64-4-cores
arch: arm64
steps:
- run: lsb_release -a
- run: uname -a
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
cache-dependency-path: 'pnpm-lock.yaml'
- name: Cache .electron-gyp
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.electron-gyp
key: electron-gyp-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
@ -180,7 +207,7 @@ jobs:
# - name: Setup sccache
# uses: mozilla-actions/sccache-action@054db53350805f83040bf3e6e9b8cf5a139aa7c9 # v0.0.7
# - name: Restore sccache
# uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
# uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
# with:
# path: ${{ env.SCCACHE_PATH }}
# key: sccache-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'patches/**') }}
@ -192,17 +219,21 @@ jobs:
# CXX: sccache g++
# SCCACHE_GHA_ENABLED: "true"
NPM_CONFIG_LOGLEVEL: verbose
# We rebuild in `electron:install-app-deps` that doesn't look at this
# environment variable
NPM_CONFIG_NODE_GYP: echo
- run: pnpm run generate
- run: pnpm run prepare-beta-build
- run: pnpm run generate
- name: Create bundle
run: pnpm run build:esbuild:prod
- name: Create preload cache
run: xvfb-run --auto-servernum pnpm run build:preload-cache
env:
ARTIFACTS_DIR: artifacts/linux
- name: Set Linux build target architecture
run: pnpm run prepare-linux-build deb ${{ matrix.arch }}
- name: Build with packaging .deb file
run: pnpm run build:release --publish=never
if: github.ref == 'refs/heads/main'
@ -222,15 +253,15 @@ jobs:
- name: Upload installer size
if: ${{ github.repository == 'signalapp/Signal-Desktop-Private' && github.ref == 'refs/heads/main' }}
run: node ts/scripts/publish-installer-size.node.js linux
run: node scripts/publish-installer-size.mjs linux-${{ matrix.arch }}
- run: xvfb-run --auto-servernum pnpm run test-node
- name: Clone backup integration tests
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: 'signalapp/Signal-Message-Backup-Tests'
ref: '33b3d0cd4367a898f5f8b4a2c57ee12ba7ec38ea'
ref: 'a0f900243210efbedc72f0907c5d2f140385daa4'
path: 'backup-integration-tests'
- run: xvfb-run --auto-servernum pnpm run test-electron
@ -247,43 +278,46 @@ jobs:
- name: Upload artifacts on failure
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
path: artifacts
windows:
name: Windows
needs: lint
runs-on: windows-latest-8-cores
timeout-minutes: 30
steps:
- run: systeminfo
- run: git config --global core.autocrlf false
- run: git config --global core.eol lf
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
cache-dependency-path: 'pnpm-lock.yaml'
- name: Cache .electron-gyp
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.electron-gyp
key: electron-gyp-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- run: touch noop.js
- name: Install Desktop node_modules
run: pnpm install
env:
NPM_CONFIG_LOGLEVEL: verbose
NPM_CONFIG_NODE_GYP: ${{ github.workspace }}\noop.js
- run: pnpm run generate
- run: pnpm run test-node
- run: copy package.json temp.json
- run: del package.json
- run: type temp.json | findstr /v certificateSubjectName | findstr /v certificateSha1 > package.json
- run: pnpm run prepare-beta-build
- run: pnpm run generate
- run: pnpm run test-node
- name: Create bundle
run: pnpm run build:esbuild:prod
- name: Create preload cache
run: pnpm run build:preload-cache
env:
@ -302,7 +336,7 @@ jobs:
- name: Upload installer size
if: ${{ github.repository == 'signalapp/Signal-Desktop-Private' && github.ref == 'refs/heads/main' }}
run: node ts/scripts/publish-installer-size.node.js windows
run: node scripts/publish-installer-size.mjs windows
- run: pnpm run test-electron
env:
@ -315,7 +349,7 @@ jobs:
- name: Upload artifacts on failure
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
path: artifacts
@ -329,11 +363,11 @@ jobs:
working-directory: sticker-creator
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: '.nvmrc'
@ -352,14 +386,12 @@ jobs:
- name: Check Sticker Creator linting
run: pnpm run lint
- name: Run tests
run: pnpm test -- --run
mock-tests:
name: Mock Tests
needs: lint
continue-on-error: true
strategy:
fail-fast: false
matrix:
workerIndex: [0, 1, 2, 3]
@ -374,18 +406,18 @@ jobs:
run: uname -a
- name: Clone Desktop repo
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
cache-dependency-path: 'pnpm-lock.yaml'
- name: Cache .electron-gyp
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.electron-gyp
key: electron-gyp-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
@ -399,7 +431,7 @@ jobs:
# - name: Setup sccache
# uses: mozilla-actions/sccache-action@054db53350805f83040bf3e6e9b8cf5a139aa7c9 # v0.0.7
# - name: Restore sccache
# uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
# uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
# with:
# path: ${{ env.SCCACHE_PATH }}
# key: sccache-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'patches/**') }}
@ -407,6 +439,7 @@ jobs:
- name: Install Desktop node_modules
run: |
pnpm install
./node_modules/.bin/install-electron
sudo chown root node_modules/.pnpm/electron@*/node_modules/electron/dist/chrome-sandbox
sudo chmod 4755 node_modules/.pnpm/electron@*/node_modules/electron/dist/chrome-sandbox
env:
@ -414,11 +447,12 @@ jobs:
# CXX: sccache g++
# SCCACHE_GHA_ENABLED: "true"
NPM_CONFIG_LOGLEVEL: verbose
# We rebuild in `electron:install-app-deps` that doesn't look at this
# environment variable
NPM_CONFIG_NODE_GYP: echo
- name: Build typescript
run: pnpm run generate
- name: Bundle
run: pnpm run build:esbuild:prod
- name: Create preload cache
run: xvfb-run --auto-servernum pnpm run build:preload-cache
env:
@ -452,45 +486,301 @@ jobs:
- name: Upload mock server test logs on failure
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: logs-${{ matrix.workerIndex }}
path: artifacts
check-min-os-version:
name: Check Min OS Version
needs: lint
continue-on-error: true
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04-8-cores, macos-latest, windows-latest-8-cores]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- run: uname -a
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
cache-dependency-path: 'pnpm-lock.yaml'
- name: Cache .electron-gyp
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.electron-gyp
key: electron-gyp-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install Desktop node_modules
if: matrix.os != 'windows-latest'
run: pnpm install
env:
NPM_CONFIG_LOGLEVEL: verbose
# We rebuild in `electron:install-app-deps` that doesn't look at this
# environment variable
NPM_CONFIG_NODE_GYP: echo
- run: touch noop.js
if: matrix.os == 'windows-latest'
- name: Install Desktop node_modules on Windows
if: matrix.os == 'windows-latest'
run: pnpm install
env:
NPM_CONFIG_LOGLEVEL: verbose
# We rebuild in `electron:install-app-deps` that doesn't look at this
# environment variable
NPM_CONFIG_NODE_GYP: ${{ github.workspace }}\noop.js
- run: pnpm generate:phase-0
- name: Run OS version check
run: |
node ts/scripts/check-min-os-version.node.js
node scripts/check-min-os-version.mjs
danger:
name: Danger
runs-on: ubuntu-latest
timeout-minutes: 30
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: '.nvmrc'
package-manager-cache: false # Avoid cache key clashes
- name: Install danger node_modules
run: pnpm install
working-directory: danger
- name: Run DangerJS
run: pnpm run danger:ci
working-directory: danger
env:
DANGER_GITHUB_API_TOKEN: ${{ secrets.AUTOMATED_GITHUB_PAT }}
storybook:
name: Storybook
runs-on: ubuntu-latest-8-cores
timeout-minutes: 30
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
cache-dependency-path: 'pnpm-lock.yaml'
- name: Cache .electron-gyp
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.electron-gyp
key: electron-gyp-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
# - name: Setup sccache
# uses: mozilla-actions/sccache-action@054db53350805f83040bf3e6e9b8cf5a139aa7c9 # v0.0.7
# - name: Restore sccache
# uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
# id: cache-sccache
# with:
# path: ${{ env.SCCACHE_PATH }}
# key: sccache-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'patches/**') }}
- name: Install Desktop node_modules
run: pnpm install
env:
# CC: sccache gcc
# CXX: sccache g++
# SCCACHE_GHA_ENABLED: "true"
NPM_CONFIG_LOGLEVEL: verbose
# We rebuild in `electron:install-app-deps` that doesn't look at this
# environment variable
NPM_CONFIG_NODE_GYP: echo
- run: pnpm run build:storybook
- run: ./node_modules/.bin/playwright install chromium
- run: ./node_modules/.bin/run-p --race test:storybook:serve test:storybook:test
benchmark:
name: Benchmark
strategy:
matrix:
metric:
- startup
- send
- groupSend
- largeGroupSendWithBlocks
- largeGroupSend
- convoOpen
- callHistorySearch
- backup
include:
- metric: startup
script: ts/test-mock/benchmarks/startup_bench.node.js
runCount: 10
- metric: send
script: ts/test-mock/benchmarks/send_bench.node.js
runCount: 100
- metric: groupSend
script: ts/test-mock/benchmarks/group_send_bench.node.js
runCount: 100
conversationSize: 500
- metric: largeGroupSendWithBlocks
script: ts/test-mock/benchmarks/group_send_bench.node.js
runCount: 50
conversationSize: 500
groupSize: 500
contactCount: 500
blockedCount: 10
discardCount: 2
- metric: largeGroupSend
script: ts/test-mock/benchmarks/group_send_bench.node.js
runCount: 20
conversationSize: 50
groupSize: 500
contactCount: 500
discardCount: 2
- metric: convoOpen
script: ts/test-mock/benchmarks/convo_open_bench.node.js
runCount: 100
- metric: callHistorySearch
script: ts/test-mock/benchmarks/call_history_search_bench.node.js
runCount: 100
- metric: backup
script: ts/test-mock/benchmarks/backup_bench.node.js
runs-on: ubuntu-22.04-8-cores
if: ${{ github.repository == 'signalapp/Signal-Desktop-Private' }}
timeout-minutes: 30
steps:
- name: Get system specs
run: lsb_release -a
- name: Get other system specs
run: uname -a
- name: Clone Desktop repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
# - name: Setup sccache
# uses: mozilla-actions/sccache-action@054db53350805f83040bf3e6e9b8cf5a139aa7c9 # v0.0.7
# - name: Restore sccache
# uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
# id: cache-sccache
# with:
# path: ${{ env.SCCACHE_PATH }}
# key: sccache-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'patches/**') }}
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
cache-dependency-path: 'pnpm-lock.yaml'
- name: Cache .electron-gyp
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.electron-gyp
key: electron-gyp-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install xvfb and libpulse0
run: sudo apt-get install xvfb libpulse0 || (sudo apt-get update && sudo apt-get install xvfb libpulse0)
- name: Install Desktop node_modules
run: pnpm install
env:
# CC: sccache gcc
# CXX: sccache g++
# SCCACHE_GHA_ENABLED: "true"
NPM_CONFIG_LOGLEVEL: verbose
# We rebuild in `electron:install-app-deps` that doesn't look at this
# environment variable
NPM_CONFIG_NODE_GYP: echo
- name: Build typescript
run: pnpm run generate
- name: Create preload cache
run: xvfb-run --auto-servernum pnpm run build:preload-cache
- name: Set MAX_CYCLES=2 on main
if: ${{ github.ref == 'refs/heads/main' }}
run: |
echo "MAX_CYCLES=2" >> "$GITHUB_ENV"
- name: Run ${{ matrix.metric }}
run: |
set -o pipefail
xvfb-run --auto-servernum ./node_modules/.bin/tsx \
${{ matrix.script }} | tee benchmark.log
timeout-minutes: 10
env:
NODE_ENV: production
ELECTRON_ENABLE_STACK_DUMPING: on
DEBUG: 'mock:benchmarks'
ARTIFACTS_DIR: artifacts/${{ matrix.metric }}
GROUP_SIZE: ${{ matrix.groupSize }}
CONTACT_COUNT: ${{ matrix.contactCount }}
BLOCKED_COUNT: ${{ matrix.blockedCount }}
DISCARD_COUNT: ${{ matrix.discardCount }}
RUN_COUNT: ${{ matrix.runCount }}
CONVERSATION_SIZE: ${{ matrix.conversationSize }}
- name: Upload benchmark logs on failure
if: failure()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: logs
path: artifacts
- name: Clone benchmark repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: 'signalapp/Signal-Desktop-Benchmarks-Private'
path: 'benchmark-results'
token: ${{ secrets.AUTOMATED_GITHUB_PAT }}
- name: Build benchmark repo
working-directory: benchmark-results
run: |
pnpm install
pnpm run build
- name: Publish
working-directory: benchmark-results
run: |
node ./bin/publish.js ../benchmark.log desktop.ci.performance.${{ matrix.metric }}
env:
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
auto-merge-ready:
if: ${{ github.event_name == 'pull_request' && github.repository == 'signalapp/Signal-Desktop-Private' }}
name: Auto Merge Ready
needs:
- lint
- linux
- windows
- sticker-creator
- mock-tests
- check-min-os-version
- danger
- storybook
- benchmark
runs-on: ubuntu-latest
steps:
- name: Ok
run: echo ok

View File

@ -9,6 +9,7 @@ on:
- '[0-9]+.[0-9]+.x'
jobs:
linux:
name: Commit Title Check
runs-on: ubuntu-latest
if: ${{ github.repository == 'signalapp/Signal-Desktop-Private' }}
steps:

View File

@ -1,28 +0,0 @@
# Copyright 2020 Signal Messenger, LLC
# SPDX-License-Identifier: AGPL-3.0-only
name: Danger
on:
pull_request:
jobs:
danger:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0 # fetch all history
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
with:
node-version-file: '.nvmrc'
package-manager-cache: false # Avoid cache key clashes
- name: Install danger node_modules
run: cd danger && pnpm install
- name: Run DangerJS
run: pnpm run danger:ci
env:
DANGER_GITHUB_API_TOKEN: ${{ secrets.AUTOMATED_GITHUB_PAT }}

View File

@ -13,17 +13,17 @@ jobs:
if: ${{ github.repository == 'signalapp/Signal-Desktop-Private' }}
timeout-minutes: 30
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
cache-dependency-path: 'pnpm-lock.yaml'
- name: Cache .electron-gyp
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.electron-gyp
key: electron-gyp-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
@ -31,7 +31,7 @@ jobs:
# - name: Setup sccache
# uses: mozilla-actions/sccache-action@054db53350805f83040bf3e6e9b8cf5a139aa7c9 # v0.0.7
# - name: Restore sccache
# uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
# uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
# id: cache-sccache
# with:
# path: ${{ env.SCCACHE_PATH }}
@ -44,18 +44,21 @@ jobs:
# CXX: sccache g++
# SCCACHE_GHA_ENABLED: "true"
NPM_CONFIG_LOGLEVEL: verbose
# We rebuild in `electron:install-app-deps` that doesn't look at this
# environment variable
NPM_CONFIG_NODE_GYP: echo
- run: pnpm run build:storybook
- run: ./node_modules/.bin/playwright install chromium
- run: ./node_modules/.bin/run-p --race test:storybook:serve test:storybook:test
env:
ARTIFACTS_DIR: stories
- run: pnpm run build:esbuild
- run: node ts/scripts/compile-stories-icu-lookup.node.js stories
- run: pnpm run build:rolldown
- run: node scripts/compile-stories-icu-lookup.mjs stories
- name: Upload test artifacts
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: desktop-test-icu
path: stories
@ -63,7 +66,7 @@ jobs:
- name: Upload release artifacts
if: github.event_name != 'workflow_dispatch'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: desktop-${{ github.ref_name }}-icu
path: stories

View File

@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.AUTOMATED_GITHUB_PAT }}
repository: signalapp/Signal-Notes-Action-Private

View File

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.AUTOMATED_GITHUB_PAT }}
repository: signalapp/Signal-Release-Notes-Action-Private

View File

@ -20,6 +20,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
actions: write
issues: write
steps:
- name: Log info
run: |
@ -37,7 +38,7 @@ jobs:
- name: Restore previous version file from cache
id: restore-cache-version
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
key: ${{ matrix.package }}-version-git-ref-txt
path: ~/version-git-ref.txt
@ -66,6 +67,9 @@ jobs:
echo "$VERSION_GIT_TAG" > ~/version-git-ref.txt
echo "tag=$VERSION_GIT_TAG" >> $GITHUB_OUTPUT
BRANCH_PREFIX=$(echo "$VERSION_GIT_TAG" | grep -oE '[0-9]+\.[0-9]+')
echo "git_branch=$BRANCH_PREFIX.x" >> $GITHUB_OUTPUT
- name: Determine if a build is needed
id: should-run
run: |
@ -88,7 +92,18 @@ jobs:
- name: Cache latest version
if: steps.should-run.outputs.result == 'true'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
key: ${{ matrix.package }}-version-git-ref-txt
path: ~/version-git-ref.txt
- name: Open issue on failure
if: ${{ failure() && github.repository == 'signalapp/Signal-Desktop-Private' }}
run: |
curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${{ github.repository }}/issues \
-d '{"title":"Reproducible build scheduler failed: ${{ steps.latest-version.outputs.tag }}","body":"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"},"labels":["${{ steps.latest-version.outputs.git_branch }}"]}'

View File

@ -21,6 +21,9 @@ jobs:
linux:
name: Linux deb
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- name: Get system specs
run: lsb_release -a
@ -37,6 +40,9 @@ jobs:
PARSED_VERSION=$(echo "${{ inputs.version_tag }}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+.*' | tr '-' '~')
echo "PACKAGE_VERSION=$PARSED_VERSION" >> "$GITHUB_ENV"
BRANCH_PREFIX=$(echo "${{ inputs.version_tag }}" | grep -oE '[0-9]+\.[0-9]+')
echo "git_branch=$BRANCH_PREFIX.x" >> $GITHUB_OUTPUT
echo "# Reproducing ${{ inputs.package }} Linux deb" >> $GITHUB_STEP_SUMMARY
echo "## Version: ${{ inputs.version_tag }}" >> $GITHUB_STEP_SUMMARY
@ -84,7 +90,7 @@ jobs:
fi
- name: Clone Desktop git repo
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ steps.app_info.outputs.git_ref }}
@ -127,6 +133,7 @@ jobs:
run: |
cd release
BUILT_FILE=$(ls | grep deb | tail -1)
echo "built_file=$BUILT_FILE" >> $GITHUB_OUTPUT
ACTUAL_SHA512=$(sha512sum $BUILT_FILE | cut -d' ' -f1)
echo "actual_sha512=$ACTUAL_SHA512" >> $GITHUB_OUTPUT
env:
@ -153,3 +160,24 @@ jobs:
echo "❌ Build checksum verification failed!" >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Run diffoscope to find diffs
if: failure()
run: |
apt-get download "$PACKAGE_NAME=$PACKAGE_VERSION"
docker run --rm -w $(pwd) -v $(pwd):$(pwd):ro \
registry.salsa.debian.org/reproducible-builds/diffoscope@sha256:51fa7187f093c4cb75b386e7e4caa38ea5783b4a9204b517dc1c91dada209421 --no-progress --max-text-report-size 5000 --max-report-size 5000 --max-diff-block-lines 100 \
release/${{ steps.build_checksum.outputs.built_file }} \
${{ steps.download.outputs.deb_file }}
- name: Open issue on failure
if: ${{ failure() && github.repository == 'signalapp/Signal-Desktop-Private' }}
run: |
curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${{ github.repository }}/issues \
-d '{"title":"Reproducible build failed: ${{ inputs.version_tag }}","body":"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}","labels":["${{ steps.app_info.outputs.git_branch }}"]}'

View File

@ -1,50 +0,0 @@
# Copyright 2023 Signal Messenger, LLC
# SPDX-License-Identifier: AGPL-3.0-only
name: Stories
on:
push:
branches:
- development
- main
- '[0-9]+.[0-9]+.x'
pull_request:
jobs:
test:
runs-on: ubuntu-latest-8-cores
timeout-minutes: 30
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
cache-dependency-path: 'pnpm-lock.yaml'
- name: Cache .electron-gyp
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.electron-gyp
key: electron-gyp-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
# - name: Setup sccache
# uses: mozilla-actions/sccache-action@054db53350805f83040bf3e6e9b8cf5a139aa7c9 # v0.0.7
# - name: Restore sccache
# uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
# id: cache-sccache
# with:
# path: ${{ env.SCCACHE_PATH }}
# key: sccache-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'patches/**') }}
- name: Install Desktop node_modules
run: pnpm install
env:
# CC: sccache gcc
# CXX: sccache g++
# SCCACHE_GHA_ENABLED: "true"
NPM_CONFIG_LOGLEVEL: verbose
- run: pnpm run build:storybook
- run: ./node_modules/.bin/playwright install chromium
- run: ./node_modules/.bin/run-p --race test:storybook:serve test:storybook:test

5
.gitignore vendored
View File

@ -5,6 +5,7 @@ coverage/*
build/curve25519_compiled.js
build/compact-locales
build/*.policy
build/emoji-data.json
stylesheets/*.css.map
/dist
.DS_Store
@ -17,14 +18,11 @@ release/
/sql/
/start.sh
.eslintcache
.stylelintcache
tsconfig.tsbuildinfo
.smartling-source.sh
# generated files
js/components.js
js/util_worker.js
libtextsecure/components.js
stylesheets/*.css
!stylesheets/tailwind-config.css
@ -39,6 +37,7 @@ build/ICUMessageParams.d.ts
build/**/*.js
app/*.js
ts/**/*.js
!ts/windows/main/tsx.js
ts/protobuf/*.d.ts
# CSS Modules

View File

@ -1,3 +1,4 @@
{
"checkLeaks": true
"checkLeaks": true,
"node-option": ["import=tsx"]
}

9
.npmrc
View File

@ -1,9 +0,0 @@
legacy-peer-deps=true
public-hoist-pattern[]=*eslint-*
minimum-release-age=14400
minimum-release-age-exclude[]=@signalapp/*
minimum-release-age-exclude[]=@indutny/*
minimum-release-age-exclude[]=@types/*
minimum-release-age-exclude[]=electron
minimum-release-age-exclude[]=react
minimum-release-age-exclude[]=react-dom

2
.nvmrc
View File

@ -1 +1 @@
24.14.0
24.15.0

37
.oxlint/plugin.mjs Normal file
View File

@ -0,0 +1,37 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { enforceArrayBuffer } from './rules/enforceArrayBuffer.mjs';
import { enforceFileSuffix } from './rules/enforceFileSuffix.mjs';
import { enforceLicenseComments } from './rules/enforceLicenseComments.mjs';
import { enforceTw } from './rules/enforceTw.mjs';
import { enforceTypeAliasReadonlyDeep } from './rules/enforceTypeAliasReadonlyDeep.mjs';
import { noDisabledTests } from './rules/noDisabledTests.mjs';
import { noExtraneousDependencies } from './rules/noExtraneousDependencies.mjs';
import { noFocusedTests } from './rules/noFocusedTests.mjs';
import { noForIn } from './rules/noForIn.mjs';
import { noRestrictedPaths } from './rules/noRestrictedPaths.mjs';
import { noThen } from './rules/noThen.mjs';
/** @type {import("@typescript-eslint/utils").TSESLint.Linter.Plugin} */
const plugin = {
meta: {
name: 'signal-desktop',
version: '0.0.0',
},
rules: {
'enforce-array-buffer': enforceArrayBuffer,
'enforce-file-suffix': enforceFileSuffix,
'enforce-license-comments': enforceLicenseComments,
'enforce-tw': enforceTw,
'enforce-type-alias-readonlydeep': enforceTypeAliasReadonlyDeep,
'no-disabled-tests': noDisabledTests,
'no-extraneous-dependencies': noExtraneousDependencies,
'no-focused-tests': noFocusedTests,
'no-for-in': noForIn,
'no-restricted-paths': noRestrictedPaths,
'no-then': noThen,
},
};
export default plugin;

View File

@ -1,12 +1,18 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { ESLintUtils } from '@typescript-eslint/utils';
/** @type {import("eslint").Rule.RuleModule} */
module.exports = {
export const enforceArrayBuffer = ESLintUtils.RuleCreator.withoutDocs({
name: 'enforce-array-buffer',
meta: {
type: 'problem',
hasSuggestions: true,
fixable: true,
fixable: 'code',
messages: {
shouldUseArrayBuffer: `Should be {{replacement}}`,
},
schema: [],
defaultOptions: [],
},
create(context) {
return {
@ -24,13 +30,14 @@ module.exports = {
return;
}
if (node.typeParameters != null) {
if (node.typeArguments != null) {
return;
}
context.report({
node,
message: `Should be ${replacement}`,
messageId: 'shouldUseArrayBuffer',
data: { replacement },
fix(fixer) {
return [fixer.replaceTextRange(node.range, replacement)];
},
@ -38,4 +45,4 @@ module.exports = {
},
};
},
};
});

View File

@ -1,31 +1,12 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { enforceArrayBuffer } from './enforceArrayBuffer.mjs';
import { RuleTester } from '@typescript-eslint/rule-tester';
const rule = require('./enforce-array-buffer');
const RuleTester = require('eslint').RuleTester;
const ruleTester = new RuleTester();
// avoid triggering mocha's global leak detection
require('@typescript-eslint/parser');
const ruleTester = new RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
});
const EXPECTED_ARRAY_ERROR = {
message: 'Should be Uint8Array<ArrayBuffer>',
type: 'TSTypeReference',
};
const EXPECTED_BUFFER_ERROR = {
message: 'Should be Buffer<ArrayBuffer>',
type: 'TSTypeReference',
};
ruleTester.run('enforce-array-buffer', rule, {
ruleTester.run('enforce-array-buffer', enforceArrayBuffer, {
valid: [
{ code: 'type T = number;' },
{ code: 'type T = Uint16Array;' },
@ -52,32 +33,32 @@ ruleTester.run('enforce-array-buffer', rule, {
{
code: `type T = Uint8Array`,
output: `type T = Uint8Array<ArrayBuffer>`,
errors: [EXPECTED_ARRAY_ERROR],
errors: [{ messageId: 'shouldUseArrayBuffer' }],
},
{
code: `function f(): Uint8Array {}`,
output: `function f(): Uint8Array<ArrayBuffer> {}`,
errors: [EXPECTED_ARRAY_ERROR],
errors: [{ messageId: 'shouldUseArrayBuffer' }],
},
{
code: `function f(p: Uint8Array) {}`,
output: `function f(p: Uint8Array<ArrayBuffer>) {}`,
errors: [EXPECTED_ARRAY_ERROR],
errors: [{ messageId: 'shouldUseArrayBuffer' }],
},
{
code: `let v: Uint8Array;`,
output: `let v: Uint8Array<ArrayBuffer>;`,
errors: [EXPECTED_ARRAY_ERROR],
errors: [{ messageId: 'shouldUseArrayBuffer' }],
},
{
code: `let v: { p: Uint8Array };`,
output: `let v: { p: Uint8Array<ArrayBuffer> };`,
errors: [EXPECTED_ARRAY_ERROR],
errors: [{ messageId: 'shouldUseArrayBuffer' }],
},
{
code: `type T = Buffer`,
output: `type T = Buffer<ArrayBuffer>`,
errors: [EXPECTED_BUFFER_ERROR],
errors: [{ messageId: 'shouldUseArrayBuffer' }],
},
],
});

View File

@ -1,5 +1,23 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { ESLintUtils } from '@typescript-eslint/utils';
import { getReferenceType } from './utils/getReferenceType.mjs';
import { isStringLiteral } from './utils/astUtils.mjs';
import { assert } from './utils/assert.mjs';
/**
* @typedef {import("@typescript-eslint/utils").TSESTree.Node} Node
* @typedef {import("@typescript-eslint/utils").TSESTree.ImportDeclaration} ImportDeclaration
* @typedef {import("@typescript-eslint/utils").TSESTree.ExportAllDeclaration} ExportAllDeclaration
* @typedef {import("@typescript-eslint/utils").TSESTree.ExportNamedDeclaration} ExportNamedDeclaration
* @typedef {import("@typescript-eslint/utils").TSESTree.ImportClause} ImportClause
* @typedef {import("@typescript-eslint/utils").TSESTree.ExportSpecifier} ExportSpecifier
*/
/**
* @typedef {'std' | 'node' | 'dom' | 'preload' | 'main'} Suffix
*/
const ELECTRON_MAIN_MODULES = new Set([
'app',
@ -73,9 +91,11 @@ const NODE_PACKAGES = new Set([
'fs-xattr',
'got',
'growing-file',
'http-proxy-agent',
'https-proxy-agent',
'node-fetch',
'proxy-agent',
'read-last-lines',
'socks-proxy-agent',
'split2',
'write-file-atomic',
@ -97,13 +117,10 @@ const NODE_PACKAGES = new Set([
'endanger',
'enhanced-resolve',
'enquirer',
'esbuild',
'execa',
'html-webpack-plugin',
'http-server',
'json-to-ast',
'log-symbols',
'mini-css-extract-plugin',
'node-gyp',
'node-gyp-build',
'npm-run-all',
@ -111,13 +128,13 @@ const NODE_PACKAGES = new Set([
'pe-library',
'pixelmatch',
'playwright',
'postcss',
'postcss-loader',
'prettier',
'prettier-plugin-tailwindcss',
'react-devtools',
'react-devtools-core',
'resolve-url-loader',
'rolldown',
'sass',
'sass-loader',
'style-loader',
@ -128,8 +145,7 @@ const NODE_PACKAGES = new Set([
'svgo',
'synckit',
'tailwindcss',
'terser-webpack-plugin',
'ts-node',
'tsx',
'typescript',
'wait-on',
'webpack',
@ -147,10 +163,8 @@ const DOM_PACKAGES = new Set([
'@tanstack/react-virtual',
'blob-util',
'blueimp-load-image',
'copy-text-to-clipboard',
'dom-accessibility-api',
'fabric',
'focus-trap-react',
'radix-ui',
'react-aria',
'react-aria-components',
@ -170,8 +184,6 @@ const DOM_PACKAGES = new Set([
'@storybook/addon-toolbars',
'@storybook/addon-viewport',
'@storybook/addon-webpack5-compiler-swc',
'@storybook/csf',
'@storybook/preview-api',
'@storybook/react',
'@storybook/react-webpack5',
'@storybook/test',
@ -183,9 +195,6 @@ const DOM_PACKAGES = new Set([
// Packages that can run in both browser/node
const STD_PACKAGES = new Set([
'@babel/core',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-transform-runtime',
'@babel/plugin-transform-typescript',
'@babel/preset-react',
@ -198,7 +207,9 @@ const STD_PACKAGES = new Set([
'@internationalized/date',
'@react-types/shared',
'@signalapp/minimask',
'@signalapp/parchment-cjs',
'@signalapp/quill-cjs',
'@signalapp/lame',
'@typescript-eslint/eslint-plugin',
'@typescript-eslint/parser',
'axe-core',
@ -211,7 +222,6 @@ const STD_PACKAGES = new Set([
'casual',
'chai',
'chai-as-promised',
'chalk',
'changedpi',
'classnames',
'country-codes-list',
@ -221,18 +231,9 @@ const STD_PACKAGES = new Set([
'danger',
'debug',
'direction',
'emoji-datasource',
'emoji-datasource-apple',
'emoji-regex',
'emoji-regex-xs',
'eslint',
'eslint-config-airbnb-typescript-prettier',
'eslint-config-prettier',
'eslint-plugin-better-tailwindcss',
'eslint-plugin-import',
'eslint-plugin-local-rules',
'eslint-plugin-mocha',
'eslint-plugin-more',
'eslint-plugin-react',
'filesize',
'firstline',
'form-data',
@ -257,7 +258,6 @@ const STD_PACKAGES = new Set([
'p-queue',
'p-timeout',
'parsecurrency',
'pify',
'pino',
'pngjs',
'qrcode-generator',
@ -274,29 +274,60 @@ const STD_PACKAGES = new Set([
'tinykeys',
'type-fest',
'url',
'urlpattern-polyfill',
'uuid',
'zod',
]);
/** @type {import("eslint").Rule.RuleModule} */
module.exports = {
export const enforceFileSuffix = ESLintUtils.RuleCreator.withoutDocs({
name: 'enforce-file-suffix',
meta: {
type: 'problem',
hasSuggestions: false,
fixable: false,
messages: {
missingFileSuffix: 'Missing file suffix in {{source}} import',
unrecognizedFileSuffix:
'Unrecognized file suffix in {{source}}, expected: node/preload/main/std, found: {{depSuffix}}',
commonJsImportOfElectronNoAllowed:
'CJS import of electron is not allowed',
uncategorizedElectronApi:
'Uncategorized electron API: "{{name}}". ' +
'Please update .oxlint/rules/file-suffix.js and add it to ' +
'ELECTRON_MAIN_MODULES/ELECTRON_RENDERER_MODULES/' +
'ELECTRON_SHARED_MODULES',
unsupportedNamespaceImportForElectron:
'Unsupported namespace import specifier for electron',
unsupportedImportSpecifierForElectron:
'Unsupported import specifier for electron',
uncategorizedDependency:
'Uncategorized dependency "{{moduleName}}". ' +
'Please update .oxlint/rules/file-suffix.js and add it to either ' +
'of NODE_PACKAGES/DOM_PACKAGES/STD_PACKAGES',
missingFileSuffixMustBeOneOf:
'Missing file suffix. Has to be one of: node/preload/main/std',
wrongFileSuffix:
'Invalid suffix {{fileSuffix}}, expected: {{expectedSuffix}}',
invalidImportForSuffix:
'Invalid import/reference for suffix: {{expectedSuffix}}',
invalidRequireCount: 'Invalid require() argument count',
},
schema: [],
defaultOptions: [],
},
create(context) {
const { filename, sourceCode } = context;
/** @type {string} */
let fileSuffix;
/** @type {Node[]} */
const nodeUses = [];
/** @type {Node[]} */
const domUses = [];
/** @type {Node[]} */
const preloadUses = [];
/** @type {Node[]} */
const mainUses = [];
/** @type Record<Suffix, Node[][]> */
const invalidUsesBySuffix = {
std: [nodeUses, domUses, preloadUses, mainUses],
node: [domUses, preloadUses, mainUses],
@ -305,16 +336,21 @@ module.exports = {
main: [domUses, preloadUses],
};
/**
* @param {Node} node
* @param {string} source
*/
function trackLocalDep(node, source) {
if (!source.endsWith('.js')) {
if (!/\.tsx?/.test(source)) {
return;
}
const match = source.match(/\.([^.\/]+)(?:\.stories)?\.js$/);
const match = source.match(/\.([^.\/]+)(?:\.stories)?\.tsx?$/);
if (match == null) {
context.report({
node,
message: `Missing file suffix in ${source} import`,
messageId: 'missingFileSuffix',
data: { source },
});
return;
}
@ -333,13 +369,17 @@ module.exports = {
} else {
context.report({
node,
message:
`Unrecognized file suffix in ${source}, ` +
`expected: node/preload/main/std, found: ${depSuffix}`,
messageId: 'unrecognizedFileSuffix',
data: { source, depSuffix },
});
}
}
/**
* @param {Node} node
* @param {string} source
* @param {Array<ImportClause | ExportSpecifier> | null} specifiers
*/
function processUse(node, source, specifiers) {
if (source.startsWith('.')) {
trackLocalDep(node, source);
@ -356,38 +396,43 @@ module.exports = {
if (source === 'electron' && specifiers == null) {
context.report({
node,
message: 'CJS import of electron is not allowed',
messageId: 'commonJsImportOfElectronNoAllowed',
});
return;
} else if (source === 'electron') {
for (const s of specifiers) {
if (s.importKind === 'type') {
continue;
}
for (const s of specifiers ?? []) {
// We implicitly skip:
// they are used in scripts
if (s.type === 'ImportSpecifier') {
if (ELECTRON_MAIN_MODULES.has(s.imported.name)) {
if (s.importKind === 'type') {
continue;
}
/** @type {string} */
let importName;
if (s.imported.type === 'Identifier') {
importName = s.imported.name;
} else {
importName = s.imported.value;
}
if (ELECTRON_MAIN_MODULES.has(importName)) {
mainUses.push(s);
} else if (ELECTRON_RENDERER_MODULES.has(s.imported.name)) {
} else if (ELECTRON_RENDERER_MODULES.has(importName)) {
preloadUses.push(s);
} else if (ELECTRON_SHARED_MODULES.has(s.imported.name)) {
} else if (ELECTRON_SHARED_MODULES.has(importName)) {
// no-op
} else {
context.report({
node: s,
message:
`Uncategorized electron API: "${s.imported.name}". ` +
'Please update .eslint/rules/file-suffix.js and add it to ' +
'ELECTRON_MAIN_MODULES/ELECTRON_RENDERER_MODULES/' +
'ELECTRON_SHARED_MODULES',
messageId: 'uncategorizedElectronApi',
data: { name: importName },
});
}
} else if (s.type === 'ImportNamespaceSpecifier') {
// import * as electron from 'electron';
context.report({
node: s,
message: 'Unsupported namespace import specifier for electron',
messageId: 'unsupportedNamespaceImportForElectron',
});
nodeUses.push(s);
} else if (s.type === 'ImportDefaultSpecifier') {
@ -396,14 +441,20 @@ module.exports = {
} else {
context.report({
node: s,
message: 'Unsupported import specifier for electron',
messageId: 'unsupportedImportSpecifierForElectron',
});
}
}
return;
}
const [, moduleName] = source.match(/^([^@\/]+|@[^\/]+\/[^\/]+)/);
const match = source.match(/^([^@/]+|@[^/]+\/[^/]+)/);
if (match == null) {
return;
}
const [, moduleName] = match;
assert(moduleName, 'Missing moduleName');
if (NODE_PACKAGES.has(moduleName)) {
nodeUses.push(node);
} else if (source === 'react-dom/server') {
@ -416,60 +467,81 @@ module.exports = {
} else if (!STD_PACKAGES.has(moduleName)) {
context.report({
node,
message:
`Uncategorized dependency "${moduleName}". ` +
'Please update .eslint/rules/file-suffix.js and add it to either ' +
'of NODE_PACKAGES/DOM_PACKAGES/STD_PACKAGES',
messageId: 'uncategorizedDependency',
data: { moduleName },
});
}
}
/**
* @param {ImportDeclaration | ExportAllDeclaration | ExportNamedDeclaration} node
*/
function processESMReference(node) {
if (
node.importKind === 'type' ||
(node.specifiers?.length &&
node.specifiers.every(x => x.importKind === 'type'))
) {
return;
/** @type {Array<ImportClause | ExportSpecifier> | null} */
let specifiers;
if (node.type === 'ImportDeclaration') {
if (node.importKind === 'type') {
return;
}
if (node.specifiers.length > 0) {
const allTypes = node.specifiers.every(specifier => {
return (
specifier.type === 'ImportSpecifier' &&
specifier.importKind === 'type'
);
});
if (allTypes) {
return;
}
}
specifiers = node.specifiers;
} else if (node.type === 'ExportNamedDeclaration') {
specifiers = node.specifiers;
} else {
specifiers = null;
}
if (!node.source) {
return;
}
if (node.source.type !== 'Literal') {
return;
}
const {
specifiers,
source: { value: source },
} = node;
const source = node.source.value;
processUse(node, source, specifiers);
}
return {
Program: node => {
if (filename.endsWith('.d.ts')) {
if (/\.d\.m?ts$/.test(filename)) {
// Skip types
return;
}
const match = filename.match(/\.([^.\/]+)(?:\.stories)?\.(?:ts|tsx)$/);
const match = filename.match(
/\.([^.\/]+)(?:\.stories)?\.(?:ts|tsx|js|mjs)$/
);
if (match == null) {
context.report({
node: node,
message:
'Missing file suffix. Has to be one of: node/preload/main/std',
node,
messageId: 'missingFileSuffixMustBeOneOf',
});
return;
}
fileSuffix = match[1];
const matchedSuffix = match[1];
assert(matchedSuffix, 'Missing matchedSuffix');
fileSuffix = matchedSuffix;
},
'Program:exit': node => {
if (fileSuffix == null) {
return;
}
/** @type {Suffix} */
let expectedSuffix;
if (mainUses.length > 0) {
expectedSuffix = 'main';
@ -500,7 +572,8 @@ module.exports = {
if (fileSuffix !== expectedSuffix) {
context.report({
node,
message: `Invalid suffix ${fileSuffix}, expected: ${expectedSuffix}`,
messageId: 'wrongFileSuffix',
data: { fileSuffix, expectedSuffix },
});
}
@ -508,7 +581,8 @@ module.exports = {
for (const use of invalid) {
context.report({
node: use,
message: `Invalid import/reference for suffix: ${expectedSuffix}`,
messageId: 'invalidImportForSuffix',
data: { expectedSuffix },
});
}
},
@ -529,27 +603,29 @@ module.exports = {
return;
}
const scope = sourceCode.getScope(node);
const ref = scope.references.find(r => r.identifier === node.callee);
if (ref.resolved.scope.type !== 'global') {
const refType = getReferenceType(sourceCode, node.callee);
if (refType !== 'global') {
return;
}
const { arguments: args } = node;
if (args.length !== 1) {
context.report({
node,
message: 'Invalid require() argument count',
messageId: 'invalidRequireCount',
});
return;
}
const [arg] = args;
assert(arg, 'Missing arg');
/** @type {string} */
let source;
if (arg.type === 'Literal') {
if (isStringLiteral(arg)) {
source = arg.value;
} else if (
arg.type === 'TSAsExpression' &&
arg.expression.type === 'Literal'
isStringLiteral(arg.expression)
) {
source = arg.expression.value;
} else {
@ -557,23 +633,22 @@ module.exports = {
return;
}
processUse(node, source, undefined);
processUse(node, source, null);
},
Identifier(node) {
if (node.name !== 'window' && node.name !== 'document') {
return;
}
const scope = sourceCode.getScope(node);
const ref = scope.references.find(r => r.identifier === node);
if (ref == null) {
const refType = getReferenceType(sourceCode, node);
if (refType == null) {
// Not part of expression
return;
}
if (ref.resolved.scope.type !== 'global') {
if (refType !== 'global') {
return;
}
domUses.push(node);
},
};
},
};
});

View File

@ -0,0 +1,139 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { enforceFileSuffix } from './enforceFileSuffix.mjs';
import { RuleTester } from '@typescript-eslint/rule-tester';
const ruleTester = new RuleTester();
const ALLOWED_REFERENCES = /* @type {const} */ [
{
fileSuffix: 'std',
requiredLine: '',
depSuffixes: ['std'],
},
{
fileSuffix: 'dom',
requiredLine: 'window.addEventListener();',
depSuffixes: ['std', 'dom'],
},
{
fileSuffix: 'node',
requiredLine: 'require("node:fs");',
depSuffixes: ['std', 'node'],
},
{
fileSuffix: 'preload',
requiredLine: 'import { ipcRenderer } from "electron";',
depSuffixes: ['std', 'node', 'preload'],
},
{
fileSuffix: 'main',
requiredLine: 'import { autoUpdater } from "electron";',
depSuffixes: ['std', 'node', 'main'],
},
];
const DISALLOWED_REFERENCES = /* @type {const} */ [
{ fileSuffix: 'std', depSuffixes: ['dom', 'node', 'preload', 'main'] },
{ fileSuffix: 'dom', depSuffixes: ['node', 'preload', 'main'] },
{ fileSuffix: 'node', depSuffixes: ['preload', 'main'] },
{ fileSuffix: 'preload', depSuffixes: ['main'] },
{ fileSuffix: 'main', depSuffixes: ['dom', 'preload'] },
];
ruleTester.run('file-suffix', enforceFileSuffix, {
valid: [
...ALLOWED_REFERENCES.map(({ fileSuffix, requiredLine, depSuffixes }) => {
return depSuffixes.map(depSuffix => {
/** @type {const} */
return {
name: `importing ${depSuffix} from ${fileSuffix}`,
filename: `a.${fileSuffix}.ts`,
code: `
import { x } from './b.${depSuffix}.ts';
${requiredLine}
`,
languageOptions: {
globals: {
window: 'writable',
require: 'readable',
},
},
};
});
}).flat(),
{
name: 'type import should have no effect',
filename: 'a.std.ts',
code: `import type { ReadonlyDeep } from './b.dom.ts'`,
},
],
invalid: [
...DISALLOWED_REFERENCES.map(({ fileSuffix, depSuffixes }) => {
return depSuffixes.map(depSuffix => {
/** @type {const} */
return {
name: `importing ${depSuffix} from ${fileSuffix}`,
filename: `a.${fileSuffix}.ts`,
code: `import { x } from './b.${depSuffix}.ts'`,
errors: [
{
messageId: 'wrongFileSuffix',
data: { fileSuffix, expectedSuffix: depSuffix },
},
],
};
});
}).flat(),
...['dom', 'node', 'preload', 'main'].map(fileSuffix => {
/** @type {const} */
return {
name: `no ${fileSuffix} imports`,
filename: `a.${fileSuffix}.ts`,
code: '',
errors: [
{
messageId: 'wrongFileSuffix',
data: { fileSuffix, expectedSuffix: 'std' },
},
],
};
}),
// Invalid imports
{
name: 'preload in main',
filename: 'a.main.ts',
code: `
import { autoUpdater } from 'electron';
import './b.preload.ts';
`,
errors: [
{
messageId: 'invalidImportForSuffix',
data: { expectedSuffix: 'main' },
},
],
},
{
name: 'main in preload',
filename: 'a.preload.ts',
code: `
import { ipcRenderer } from 'electron';
import './b.main.ts';
`,
errors: [
{
messageId: 'wrongFileSuffix',
data: { fileSuffix: 'preload', expectedSuffix: 'main' },
},
{
messageId: 'invalidImportForSuffix',
data: { expectedSuffix: 'main' },
},
],
},
],
});

View File

@ -1,24 +1,29 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { ESLintUtils } from '@typescript-eslint/utils';
const COMMENT_LINE_1_EXACT = /^ Copyright \d{4} Signal Messenger, LLC$/;
const COMMENT_LINE_2_EXACT = /^ SPDX-License-Identifier: AGPL-3.0-only$/;
const COMMENT_LINE_1_LOOSE = /Copyright (\d{4}) Signal Messenger, LLC/;
const COMMENT_LINE_2_LOOSE = /SPDX-License-Identifier: AGPL-3.0-only/;
/** @type {import("eslint").Rule.RuleModule} */
module.exports = {
export const enforceLicenseComments = ESLintUtils.RuleCreator.withoutDocs({
meta: {
type: 'problem',
hasSuggestions: false,
fixable: true,
fixable: 'code',
messages: {
missingLicenseComment: 'Missing license comment',
},
schema: [],
defaultOptions: [],
},
create(context) {
return {
Program(node) {
let comment1 = node.comments.at(0);
let comment2 = node.comments.at(1);
const comment1 = node.comments?.at(0);
const comment2 = node.comments?.at(1);
if (
comment1?.type === 'Line' &&
@ -31,15 +36,14 @@ module.exports = {
context.report({
node,
message: 'Missing license comment',
messageId: 'missingLicenseComment',
fix(fixer) {
let year = null;
let remove = [];
const remove = [];
for (let comment of node.comments) {
let match1 = comment.value.match(COMMENT_LINE_1_LOOSE);
let match2 = comment.value.match(COMMENT_LINE_2_LOOSE);
for (const comment of node.comments ?? []) {
const match1 = comment.value.match(COMMENT_LINE_1_LOOSE);
const match2 = comment.value.match(COMMENT_LINE_2_LOOSE);
if (match1 != null) {
year = match1[1];
@ -52,7 +56,7 @@ module.exports = {
year ??= new Date().getFullYear().toString();
let insert =
const insert =
`// Copyright ${year} Signal Messenger, LLC\n` +
'// SPDX-License-Identifier: AGPL-3.0-only\n';
@ -70,4 +74,4 @@ module.exports = {
},
};
},
};
});

View File

@ -1,18 +1,30 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
const { createSyncFn } = require('synckit');
// @ts-check
import { ESLintUtils } from '@typescript-eslint/utils';
import { createSyncFn } from 'synckit';
const worker = createSyncFn(require.resolve('./enforce-tw.worker.js'));
/**
* @typedef {import("@typescript-eslint/utils").TSESTree.Node} Node
*/
/** @type {import("eslint").Rule.RuleModule} */
module.exports = {
const worker = createSyncFn(import.meta.resolve('./enforceTw.worker.mjs'));
export const enforceTw = ESLintUtils.RuleCreator.withoutDocs({
name: 'enforce-tw',
meta: {
type: 'problem',
hasSuggestions: true,
fixable: true,
messages: {
needsTw: 'Tailwind classes must be wrapped with tw()',
},
schema: [],
defaultOptions: [],
},
create(context) {
/**
* @param {string} input
* @param {Node} node
*/
function check(input, node) {
if (typeof input !== 'string') {
throw new Error(`Unexpected input ${input} for node type ${node.type}`);
@ -35,11 +47,14 @@ module.exports = {
column: node.loc.start.column + index + length,
},
},
message: 'Tailwind classes must be wrapped with tw()',
messageId: 'needsTw',
});
}
}
/**
* @param {Node} node
*/
function traverse(node) {
if (node.type === 'Literal') {
if (typeof node.value === 'string') {
@ -47,14 +62,16 @@ module.exports = {
}
// ignore other literals
} else if (node.type === 'TemplateLiteral') {
for (let element of node.quasis) {
for (const element of node.quasis) {
traverse(element);
}
for (let expression of node.expressions) {
for (const expression of node.expressions) {
traverse(expression);
}
} else if (node.type === 'TemplateElement') {
check(node.value.cooked, node);
if (node.value.cooked != null) {
check(node.value.cooked, node);
}
} else if (node.type === 'JSXExpressionContainer') {
traverse(node.expression);
} else if (node.type === 'ConditionalExpression') {
@ -74,7 +91,7 @@ module.exports = {
throw new Error(`Unexpected binary operator: ${node.operator}`);
}
} else if (node.type === 'ObjectExpression') {
for (let prop of node.properties) {
for (const prop of node.properties) {
traverse(prop);
}
} else if (node.type === 'Property') {
@ -93,8 +110,10 @@ module.exports = {
throw new Error(`Unexpected property key type: ${node.key.type}`);
}
} else if (node.type === 'ArrayExpression') {
for (let element of node.elements) {
traverse(element);
for (const element of node.elements) {
if (element != null) {
traverse(element);
}
}
} else if (node.type === 'Identifier') {
// ignore
@ -111,15 +130,17 @@ module.exports = {
CallExpression(node) {
if (node.callee.type !== 'Identifier') return;
if (node.callee.name !== 'classNames') return;
for (let arg of node.arguments) {
for (const arg of node.arguments) {
traverse(arg);
}
},
JSXAttribute(node) {
if (node.name.type !== 'JSXIdentifier') return;
if (node.name.name !== 'className') return;
traverse(node.value);
if (node.value != null) {
traverse(node.value);
}
},
};
},
};
});

View File

@ -0,0 +1,69 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { enforceTw } from './enforceTw.mjs';
import { RuleTester } from '@typescript-eslint/rule-tester';
const ruleTester = new RuleTester({
languageOptions: {
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
});
ruleTester.run('enforce-tw', enforceTw, {
valid: [
{ code: `classNames("foo")` },
{ code: `<div className="foo"/>` },
{ code: `tw("flex")` },
],
invalid: [
{
code: `classNames("flex")`,
errors: [{ messageId: 'needsTw' }],
},
{
code: `<div className="flex"/>`,
errors: [{ messageId: 'needsTw' }],
},
{
code: `<div className={"flex"}/>`,
errors: [{ messageId: 'needsTw' }],
},
{
code: `classNames("foo", "flex")`,
errors: [{ messageId: 'needsTw' }],
},
{
code: `classNames(cond ? "foo" : "flex")`,
errors: [{ messageId: 'needsTw' }],
},
{
code: `classNames(cond ? "flex" : "foo")`,
errors: [{ messageId: 'needsTw' }],
},
{
code: `classNames(cond && "flex")`,
errors: [{ messageId: 'needsTw' }],
},
{
code: `classNames(cond || "flex")`,
errors: [{ messageId: 'needsTw' }],
},
{
code: `classNames(cond ?? "flex")`,
errors: [{ messageId: 'needsTw' }],
},
{
code: `classNames("foo" + "flex")`,
errors: [{ messageId: 'needsTw' }],
},
{
code: `classNames("flex" + "foo")`,
errors: [{ messageId: 'needsTw' }],
},
],
});

View File

@ -1,12 +1,13 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
const { runAsWorker } = require('synckit');
const enhancedResolve = require('enhanced-resolve');
const tailwind = require('tailwindcss');
const path = require('node:path');
const fs = require('node:fs');
// @ts-check
import { runAsWorker } from 'synckit';
import enhancedResolve from 'enhanced-resolve';
import * as tailwind from 'tailwindcss';
import path from 'node:path';
import fs from 'node:fs';
const rootDir = path.join(__dirname, '../..');
const rootDir = path.join(import.meta.dirname, '../..');
const tailwindCssPath = path.join(rootDir, 'stylesheets/tailwind-config.css');
async function loadDesignSystem() {
@ -21,12 +22,13 @@ async function loadDesignSystem() {
tailwindCss,
{
base: path.dirname(tailwindCssPath),
loadStylesheet(id, base) {
async loadStylesheet(id, base) {
const resolved = resolver(base, id);
if (!resolved) {
return { base: '', content: '' };
return { path: '', base: '', content: '' };
}
return {
path: resolved,
base: path.dirname(resolved),
content: fs.readFileSync(resolved, 'utf-8'),
};
@ -39,12 +41,17 @@ async function loadDesignSystem() {
let cachedDesignSystem = null;
runAsWorker(async classNames => {
/**
* @param {Array<string>} classNames
*/
async function worker(classNames) {
cachedDesignSystem ??= await loadDesignSystem();
const designSystem = cachedDesignSystem;
const css = designSystem.candidatesToCss(classNames);
const tailwindClassNames = classNames.filter((_, index) => {
return css.at(index) !== null;
return css.at(index) != null;
});
return tailwindClassNames;
});
}
runAsWorker(worker);

View File

@ -0,0 +1,75 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { ESLintUtils } from '@typescript-eslint/utils';
import { assert } from './utils/assert.mjs';
/**
* @typedef {import("@typescript-eslint/utils").TSESTree.Node} Node
* @typedef {import("@typescript-eslint/utils").TSESLint.Scope.Scope} Scope
*/
/**
* @param {Node} node
* @param {Scope} scope
*/
function isReadOnlyDeep(node, scope) {
if (node.type !== 'TSTypeReference') {
return false;
}
const reference = scope.references.find(ref => {
return ref.identifier === node.typeName;
});
const variable = reference?.resolved;
if (variable == null) {
return false;
}
const defs = variable.defs;
if (defs.length !== 1) {
return false;
}
const [def] = defs;
assert(def, 'Missing def');
return (
def.type === 'ImportBinding' &&
def.parent.type === 'ImportDeclaration' &&
def.parent.source.type === 'Literal' &&
def.parent.source.value === 'type-fest'
);
}
export const enforceTypeAliasReadonlyDeep = ESLintUtils.RuleCreator.withoutDocs(
{
name: 'enforce-type-alias-readonlydeep',
meta: {
type: 'problem',
messages: {
needsReadonlyDeep:
'Type aliases must be wrapped with ReadonlyDeep from type-fest',
},
schema: [],
defaultOptions: [],
},
create(context) {
return {
TSTypeAliasDeclaration(node) {
const scope = context.sourceCode.getScope(node);
if (isReadOnlyDeep(node.typeAnnotation, scope)) {
return;
}
context.report({
node: node.id,
messageId: 'needsReadonlyDeep',
});
},
};
},
}
);

View File

@ -0,0 +1,40 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { enforceTypeAliasReadonlyDeep } from './enforceTypeAliasReadonlyDeep.mjs';
import { RuleTester } from '@typescript-eslint/rule-tester';
const ruleTester = new RuleTester();
ruleTester.run('type-alias-readonlydeep', enforceTypeAliasReadonlyDeep, {
valid: [
{
code: `import type { ReadonlyDeep } from "type-fest"; type Foo = ReadonlyDeep<{}>`,
},
{
code: `import { ReadonlyDeep } from "type-fest"; type Foo = ReadonlyDeep<{}>`,
},
],
invalid: [
{
code: `type Foo = {}`,
errors: [{ messageId: 'needsReadonlyDeep' }],
},
{
code: `type Foo = Bar<{}>`,
errors: [{ messageId: 'needsReadonlyDeep' }],
},
{
code: `type Foo = ReadonlyDeep<{}>`,
errors: [{ messageId: 'needsReadonlyDeep' }],
},
{
code: `interface ReadonlyDeep<T> {}; type Foo = ReadonlyDeep<{}>`,
errors: [{ messageId: 'needsReadonlyDeep' }],
},
{
code: `import type { ReadonlyDeep } from "foo"; type Foo = ReadonlyDeep<{}>`,
errors: [{ messageId: 'needsReadonlyDeep' }],
},
],
});

View File

@ -0,0 +1,22 @@
{
"dependencies": {
"prod-dep": "0.0.0",
"@scoped/prod-dep": "0.0.0"
},
"devDependencies": {
"dev-dep": "0.0.0",
"@scoped/dev-dep": "0.0.0"
},
"peerDependencies": {
"peer-dep": "0.0.0",
"@scoped/peer-dep": "0.0.0"
},
"optionalDependencies": {
"optional-dep": "0.0.0",
"@scoped/optional-dep": "0.0.0"
},
"bundledDependencies": [
"bundled-dep",
"@scoped/bundled-dep"
]
}

View File

@ -0,0 +1,3 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
export {};

View File

@ -0,0 +1,3 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
export {};

View File

@ -0,0 +1,4 @@
{
"include": ["./client/**", "./server/**"],
"compilerOptions": {}
}

View File

@ -0,0 +1,64 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { ESLintUtils } from '@typescript-eslint/utils';
import { getReferenceType } from './utils/getReferenceType.mjs';
import { isPropertyAccess } from './utils/astUtils.mjs';
export const noDisabledTests = ESLintUtils.RuleCreator.withoutDocs({
name: 'no-disabled-tests',
meta: {
type: 'problem',
hasSuggestions: true,
messages: {
unexpectedDisabledTest: 'Unexpected disabled test',
removeSkip: 'Remove .skip()',
},
schema: [],
defaultOptions: [],
},
create(context) {
const { sourceCode } = context;
return {
MemberExpression(node) {
if (node.object.type !== 'Identifier') {
return;
}
let replacement;
if (node.object.name === 'describe') {
replacement = 'describe';
} else if (node.object.name === 'it') {
replacement = 'it';
} else if (node.object.name === 'test') {
replacement = 'test';
} else {
return;
}
if (!isPropertyAccess(node, 'skip')) {
return;
}
const refType = getReferenceType(sourceCode, node.object);
if (refType != null && refType !== 'global') {
return;
}
context.report({
node,
messageId: 'unexpectedDisabledTest',
suggest: [
{
messageId: 'removeSkip',
fix(fixer) {
return [fixer.replaceTextRange(node.range, replacement)];
},
},
],
});
},
};
},
});

View File

@ -0,0 +1,56 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { noDisabledTests } from './noDisabledTests.mjs';
import { RuleTester } from '@typescript-eslint/rule-tester';
const ruleTester = new RuleTester();
ruleTester.run('no-disabled-tests', noDisabledTests, {
valid: [
{ code: 'describe(() => {});' },
{ code: 'it(() => {});' },
{ code: 'test(() => {});' },
{ code: 'describe.only(() => {});' },
{ code: 'it.only(() => {});' },
{ code: 'test.only(() => {});' },
{ code: 'let describe; describe.skip(() => {});' },
{ code: 'x.describe.skip(() => {});' },
],
invalid: [
{
code: `describe.skip(() => {});`,
suggestion: `describe(() => {});`,
},
{
code: `it.skip(() => {});`,
suggestion: `it(() => {});`,
},
{
code: `test.skip(() => {});`,
suggestion: `test(() => {});`,
},
{
code: `describe['skip'](() => {});`,
suggestion: `describe(() => {});`,
},
{
code: `it['skip'](() => {});`,
suggestion: `it(() => {});`,
},
{
code: `test['skip'](() => {});`,
suggestion: `test(() => {});`,
},
].map(opts => {
return {
code: opts.code,
errors: [
{
messageId: 'unexpectedDisabledTest',
suggestions: [{ messageId: 'removeSkip', output: opts.suggestion }],
},
],
};
}),
});

View File

@ -0,0 +1,202 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { ESLintUtils } from '@typescript-eslint/utils';
import { readFileSync } from 'node:fs';
import { dirname } from 'node:path';
import { isBuiltin, findPackageJSON } from 'node:module';
import { createImportSourceVisitor } from './utils/createImportSourceVisitor.mjs';
/**
* @param value {unknown}
* @returns {value is Record<string, unknown>}
*/
function isObject(value) {
return typeof value === 'object' && value != null;
}
/**
* @param deps {unknown}
*/
function getDepsKeys(deps) {
return new Set(isObject(deps) ? Object.keys(deps) : null);
}
/**
* @param deps {unknown}
* @returns {Set<string>}
*/
function getBundledDepsKeys(deps) {
return Array.isArray(deps) ? new Set(deps) : getDepsKeys(deps);
}
/**
* @typedef {object} PkgDeps
* @property {Set<string>} dependencies
* @property {Set<string>} devDependencies
* @property {Set<string>} peerDependencies
* @property {Set<string>} optionalDependencies
* @property {Set<string>} bundledDependencies
*/
/** @type {Map<string, PkgDeps>} */
const PKG_DEPS_CACHE = new Map();
/** @param {string} currentFile */
function getPkgDeps(currentFile) {
const currentDir = dirname(currentFile);
const cached = PKG_DEPS_CACHE.get(currentDir);
if (cached != null) {
return cached;
}
const pkgPath = findPackageJSON('.', currentFile);
if (pkgPath == null) {
throw new Error(`Could not resolve package.json from ${currentFile}`);
}
const pkgText = readFileSync(pkgPath, 'utf8');
const pkgJson = JSON.parse(pkgText);
/** @type {PkgDeps} */
const pkgDeps = {
dependencies: getDepsKeys(pkgJson.dependencies),
devDependencies: getDepsKeys(pkgJson.devDependencies),
peerDependencies: getDepsKeys(pkgJson.peerDependencies),
optionalDependencies: getDepsKeys(pkgJson.optionalDependencies),
bundledDependencies: getBundledDepsKeys(pkgJson.bundledDependencies),
};
PKG_DEPS_CACHE.set(currentDir, pkgDeps);
return pkgDeps;
}
/** @param {string} source */
function getPackageNameFromSource(source) {
if (source.startsWith('@')) {
const [scope, name] = source.split('/', 2);
return `${scope}/${name}`;
}
const [name] = source.split('/', 1);
return name;
}
/**
* @typedef {object} Options
* @property {boolean=} devDependencies
* @property {boolean=} peerDependencies
* @property {boolean=} optionalDependencies
* @property {boolean=} bundledDependencies
*/
/** @type {[Options]} */
const defaultOptions = [
{
devDependencies: true,
peerDependencies: true,
optionalDependencies: true,
bundledDependencies: true,
},
];
export const noExtraneousDependencies = ESLintUtils.RuleCreator.withoutDocs({
name: 'no-extraneous-dependencies',
meta: {
type: 'problem',
messages: {
missingFromProjectDeps:
"'{{pkgName}}' should be listed in the project's dependencies",
wrongProjectDeps:
"'{{pkgName}}' should be listed in the project's dependencies, found in {{found}}",
},
schema: [
{
type: 'object',
properties: {
devDependencies: { type: 'boolean' },
peerDependencies: { type: 'boolean' },
optionalDependencies: { type: 'boolean' },
bundledDependencies: { type: 'boolean' },
},
additionalProperties: false,
},
],
defaultOptions,
},
create(context) {
const { sourceCode, options } = context;
const opts = {
devDependencies: options[0]?.devDependencies ?? true,
peerDependencies: options[0]?.peerDependencies ?? true,
optionalDependencies: options[0]?.optionalDependencies ?? true,
bundledDependencies: options[0]?.bundledDependencies ?? true,
};
const pkgDeps = getPkgDeps(context.physicalFilename);
return createImportSourceVisitor(sourceCode, node => {
const source = node.value;
if (
source.startsWith('.') ||
source.startsWith('/') ||
source.trim() === ''
) {
return;
}
if (isBuiltin(source)) {
return;
}
const pkgName = getPackageNameFromSource(source);
/** @type {Array<string>} */
const found = [];
if (pkgDeps.dependencies.has(pkgName)) {
return;
}
if (pkgDeps.devDependencies.has(pkgName)) {
found.push('devDependencies');
if (opts.devDependencies) {
return;
}
}
if (pkgDeps.peerDependencies.has(pkgName)) {
found.push('peerDependencies');
if (opts.peerDependencies) {
return;
}
}
if (pkgDeps.optionalDependencies.has(pkgName)) {
found.push('optionalDependencies');
if (opts.optionalDependencies) {
return;
}
}
if (pkgDeps.bundledDependencies.has(pkgName)) {
found.push('bundledDependencies');
if (opts.bundledDependencies) {
return;
}
}
if (found.length > 0) {
context.report({
node,
messageId: 'wrongProjectDeps',
data: { pkgName, found: found.join(', ') },
});
} else {
context.report({
node,
messageId: 'missingFromProjectDeps',
data: { pkgName },
});
}
});
},
});

View File

@ -0,0 +1,112 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import path from 'node:path';
import { noExtraneousDependencies } from './noExtraneousDependencies.mjs';
import { RuleTester } from '@typescript-eslint/rule-tester';
/**
* @typedef {import("./noExtraneousDependencies.mjs").Options} Options
*/
const ruleTester = new RuleTester();
const filename = path.join(
import.meta.dirname,
'fixtures/noExtraneousDependencies/package/foo.js'
);
/** @type {Options} */
const NONE = {
devDependencies: false,
peerDependencies: false,
optionalDependencies: false,
bundledDependencies: false,
};
/**
* @satisfies {Record<string, [Options]>}
*/
const opts = {
none: [NONE],
dev: [{ ...NONE, devDependencies: true }],
peer: [{ ...NONE, peerDependencies: true }],
optional: [{ ...NONE, optionalDependencies: true }],
bundled: [{ ...NONE, bundledDependencies: true }],
};
ruleTester.run('no-extraneous-dependencies', noExtraneousDependencies, {
valid: [
{ filename, code: `import a from "./a";`, options: opts.none },
{ filename, code: `import a from "../a";`, options: opts.none },
{ filename, code: `import a from "path";`, options: opts.none },
{ filename, code: `import a from "node:path";`, options: opts.none },
{ filename, code: `import a from "";`, options: opts.none },
{ filename, code: `import a from "prod-dep";`, options: opts.none },
{ filename, code: `import a from "prod-dep/nested";`, options: opts.none },
{ filename, code: `import a from "@scoped/prod-dep";`, options: opts.none },
{
filename,
code: `import a from "@scoped/prod-dep/nested";`,
options: opts.none,
},
{ filename, code: `import a from "dev-dep";`, options: opts.dev },
{ filename, code: `import a from "peer-dep";`, options: opts.peer },
{
filename,
code: `import a from "optional-dep";`,
options: opts.optional,
},
{ filename, code: `import a from "bundled-dep";`, options: opts.bundled },
],
invalid: [
{
filename,
code: `import a from "dev-dep";`,
options: opts.none,
errors: [{ messageId: 'wrongProjectDeps' }],
},
{
filename,
code: `import a from "peer-dep";`,
options: opts.none,
errors: [{ messageId: 'wrongProjectDeps' }],
},
{
filename,
code: `import a from "optional-dep";`,
options: opts.none,
errors: [{ messageId: 'wrongProjectDeps' }],
},
{
filename,
code: `import a from "bundled-dep";`,
options: opts.none,
errors: [{ messageId: 'wrongProjectDeps' }],
},
{
filename,
code: `import a from "dev-dep";`,
options: opts.peer,
errors: [{ messageId: 'wrongProjectDeps' }],
},
{
filename,
code: `import a from "dev-dep";`,
options: opts.optional,
errors: [{ messageId: 'wrongProjectDeps' }],
},
{
filename,
code: `import a from "dev-dep";`,
options: opts.bundled,
errors: [{ messageId: 'wrongProjectDeps' }],
},
{
filename,
code: `import a from "does-not-exist";`,
options: opts.bundled,
errors: [{ messageId: 'missingFromProjectDeps' }],
},
],
});

View File

@ -0,0 +1,60 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { ESLintUtils } from '@typescript-eslint/utils';
import { getReferenceType } from './utils/getReferenceType.mjs';
import { isPropertyAccess } from './utils/astUtils.mjs';
export const noFocusedTests = ESLintUtils.RuleCreator.withoutDocs({
name: 'no-focused-tests',
meta: {
type: 'problem',
hasSuggestions: true,
fixable: 'code',
messages: {
unexpectedFocusedTest: 'Unexpected focused test',
},
schema: [],
},
create(context) {
const { sourceCode } = context;
return {
MemberExpression(node) {
if (node.object.type !== 'Identifier') {
return;
}
let replacement;
if (node.object.name === 'describe') {
replacement = 'describe';
} else if (node.object.name === 'it') {
replacement = 'it';
} else if (node.object.name === 'test') {
replacement = 'test';
} else {
return;
}
if (!isPropertyAccess(node, 'only')) {
return;
}
const refType = getReferenceType(sourceCode, node.object);
if (refType != null && refType !== 'global') {
return;
}
context.report({
node,
messageId: 'unexpectedFocusedTest',
fix(fixer) {
if (node.range == null) {
return null;
}
return [fixer.replaceTextRange(node.range, replacement)];
},
});
},
};
},
});

View File

@ -0,0 +1,52 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { noFocusedTests } from './noFocusedTests.mjs';
import { RuleTester } from '@typescript-eslint/rule-tester';
const ruleTester = new RuleTester();
ruleTester.run('no-focused-tests', noFocusedTests, {
valid: [
{ code: 'describe(() => {});' },
{ code: 'it(() => {});' },
{ code: 'test(() => {});' },
{ code: 'describe.skip(() => {});' },
{ code: 'it.skip(() => {});' },
{ code: 'test.skip(() => {});' },
{ code: 'let describe; describe.only(() => {});' },
{ code: 'x.describe.only(() => {});' },
],
invalid: [
{
code: `describe.only(() => {});`,
output: `describe(() => {});`,
errors: [{ messageId: 'unexpectedFocusedTest' }],
},
{
code: `it.only(() => {});`,
output: `it(() => {});`,
errors: [{ messageId: 'unexpectedFocusedTest' }],
},
{
code: `test.only(() => {});`,
output: `test(() => {});`,
errors: [{ messageId: 'unexpectedFocusedTest' }],
},
{
code: `describe['only'](() => {});`,
output: `describe(() => {});`,
errors: [{ messageId: 'unexpectedFocusedTest' }],
},
{
code: `it['only'](() => {});`,
output: `it(() => {});`,
errors: [{ messageId: 'unexpectedFocusedTest' }],
},
{
code: `test['only'](() => {});`,
output: `test(() => {});`,
errors: [{ messageId: 'unexpectedFocusedTest' }],
},
],
});

25
.oxlint/rules/noForIn.mjs Normal file
View File

@ -0,0 +1,25 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { ESLintUtils } from '@typescript-eslint/utils';
export const noForIn = ESLintUtils.RuleCreator.withoutDocs({
name: 'no-for-in',
meta: {
type: 'problem',
messages: {
preferForOf: 'Prefer for..of loops',
},
schema: [],
},
create(context) {
return {
ForInStatement(node) {
context.report({
node,
messageId: 'preferForOf',
});
},
};
},
});

View File

@ -0,0 +1,25 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { noForIn } from './noForIn.mjs';
import { RuleTester } from '@typescript-eslint/rule-tester';
const ruleTester = new RuleTester();
ruleTester.run('no-for-in', noForIn, {
valid: [
{ code: 'for (let a of b) {}' },
{ code: 'for (;;) {}' },
{ code: 'if (a in b) {}' },
],
invalid: [
{
code: `for (let a in b) {}`,
errors: [{ messageId: 'preferForOf' }],
},
{
code: `for (a in b) {}`,
errors: [{ messageId: 'preferForOf' }],
},
],
});

View File

@ -0,0 +1,264 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { ESLintUtils } from '@typescript-eslint/utils';
import { createImportSourceVisitor } from './utils/createImportSourceVisitor.mjs';
import micromatch from 'micromatch';
import isGlob from 'is-glob';
import * as path from 'node:path';
import { assert } from './utils/assert.mjs';
import enhancedResolve from 'enhanced-resolve';
const resolver = enhancedResolve.create.sync({
extensionAlias: {
'.js': ['.ts', '.tsx', '.js'],
},
});
/**
* @param {string} fromDir
* @param {string} moduleName
*/
function resolveFrom(fromDir, moduleName) {
try {
const result = resolver(fromDir, moduleName);
if (result === false) {
return null;
}
return result;
} catch (error) {
return null;
}
}
/**
* @param {string | string[]} input
* @returns {string[]}
*/
function toArray(input) {
return Array.isArray(input) ? input : [input];
}
/**
* @param {string} filePath
* @param {string} target
*/
function containsPath(filePath, target) {
const relative = path.relative(target, filePath);
return relative === '' || !relative.startsWith('..');
}
/**
* @param {string} fileName
* @param {RegExp | string} targetPath
*/
function isMatchingTargetPath(fileName, targetPath) {
return typeof targetPath === 'string'
? containsPath(fileName, targetPath)
: targetPath.test(fileName);
}
/** @type {Map<string, RegExp | string>} */
const REGEX_CACHE = new Map();
/**
* @typedef {object} Zone
* @property {string | string[]=} target
* @property {string | string[]=} from
* @property {string[]=} except
* @property {string=} message
*/
/**
* @typedef {object} Matcher
* @property {(RegExp | string)[]} targetPaths
* @property {(RegExp | string)[]} fromPaths
* @property {(RegExp | string)[] | null} exceptPaths
* @property {string | null} message
*/
/** @type {[Options]} */
const defaultOptions = [{}];
/**
* @typedef {object} Options
* @property {Zone[]=} zones
* @property {string=} basePath
*/
export const noRestrictedPaths = ESLintUtils.RuleCreator.withoutDocs({
name: 'no-restricted-paths',
meta: {
type: 'problem',
messages: {
pathRestrictedNoMessage:
'Unexpected path "{{moduleName}}" imported in restricted zone.',
pathRestrictedWithMessage:
'Unexpected path "{{moduleName}}" imported in restricted zone. {{message}}',
},
schema: [
{
type: 'object',
properties: {
zones: {
type: 'array',
minItems: 1,
items: {
type: 'object',
properties: {
target: {
anyOf: [
{ type: 'string' },
{
type: 'array',
items: { type: 'string' },
uniqueItems: true,
minItems: 1,
},
],
},
from: {
anyOf: [
{ type: 'string' },
{
type: 'array',
items: { type: 'string' },
uniqueItems: true,
minItems: 1,
},
],
},
except: {
type: 'array',
items: {
type: 'string',
},
uniqueItems: true,
},
message: { type: 'string' },
},
additionalProperties: false,
},
},
basePath: { type: 'string' },
},
additionalProperties: false,
},
],
defaultOptions,
},
create(context) {
const { filename, sourceCode } = context;
const zones = context.options[0]?.zones ?? [];
const basePath = context.options[0]?.basePath ?? context.cwd;
const matchers = zones.map(zone => {
assert(zone.target != null, 'Zone missing `target`');
assert(zone.from != null, 'Zone missing `from`');
const zoneTarget = toArray(zone.target);
const zoneFrom = toArray(zone.from);
assert(zoneTarget.length > 0, 'Zone needs at least one `target`');
assert(zoneFrom.length > 0, 'Zone needs at least one `from`');
let zoneExcept = zone.except != null ? toArray(zone.except) : null;
if (zoneExcept?.length === 0) {
zoneExcept = null;
}
let hasGlobPatterns = false;
let hasNonGlobPatterns = false;
/** @param {string} target */
function compilePattern(target) {
const targetPath = path.resolve(basePath, target);
const cached = REGEX_CACHE.get(targetPath);
if (cached != null) {
return cached;
}
/** @type {RegExp | string} */
let result;
if (isGlob(targetPath)) {
hasGlobPatterns = true;
result = micromatch.makeRe(targetPath);
} else {
hasNonGlobPatterns = true;
result = targetPath;
}
if (hasGlobPatterns && hasNonGlobPatterns) {
throw new Error(
'Cannot have both glob and non-glob patterns in the same zone'
);
}
REGEX_CACHE.set(targetPath, result);
return result;
}
/** @type {Matcher} */
const matcher = {
targetPaths: zoneTarget.map(target => compilePattern(target)),
fromPaths: zoneFrom.map(from => compilePattern(from)),
exceptPaths: zoneExcept?.map(except => compilePattern(except)) ?? null,
message: zone.message ?? null,
};
return matcher;
});
const targetMatchers = matchers.filter(matcher => {
return matcher.targetPaths.some(targetPath => {
return isMatchingTargetPath(filename, targetPath);
});
});
if (targetMatchers.length === 0) {
return {};
}
return createImportSourceVisitor(sourceCode, source => {
const dirname = path.dirname(filename);
const moduleName = source.value;
const resolvedPath = resolveFrom(dirname, moduleName);
if (resolvedPath == null) {
return;
}
for (const matcher of targetMatchers) {
const matchesFromPath = matcher.fromPaths.some(fromPath => {
return isMatchingTargetPath(resolvedPath, fromPath);
});
if (!matchesFromPath) {
continue;
}
const matchesExceptPath = matcher.exceptPaths?.some(exceptPath => {
return isMatchingTargetPath(resolvedPath, exceptPath);
});
if (matchesExceptPath) {
continue;
}
if (matcher.message != null) {
context.report({
node: source,
messageId: 'pathRestrictedWithMessage',
data: { moduleName, message: matcher.message },
});
} else {
context.report({
node: source,
messageId: 'pathRestrictedNoMessage',
data: { moduleName },
});
}
}
});
},
});

View File

@ -0,0 +1,54 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { noRestrictedPaths } from './noRestrictedPaths.mjs';
import { RuleTester } from '@typescript-eslint/rule-tester';
import * as path from 'node:path';
const basePath = path.join(import.meta.dirname, 'fixtures/noRestrictedPaths');
const filename = path.join(basePath, 'client/entry.ts');
/**
* @param {boolean=} withMessage
* @returns {[import("./noRestrictedPaths.mjs").Options]}
*/
function opts(withMessage) {
const message = withMessage ? 'just stop it' : undefined;
return [
{ basePath, zones: [{ target: './client', from: './server', message }] },
];
}
const ruleTester = new RuleTester();
ruleTester.run('no-restricted-paths', noRestrictedPaths, {
valid: [
{ filename, options: opts(), code: `import b from './client.ts';` },
{ filename, options: opts(), code: `import b from './client.js';` },
{ filename, options: opts(), code: `import b from '../client/client.ts';` },
{ filename, options: opts(), code: `import b from './nonexistant';` },
{ filename, options: opts(), code: `import b from 'node:path';` },
{ filename, options: opts(), code: `import b from 'react';` },
{ filename, options: opts(), code: `import b from 'fake-module';` },
],
invalid: [
{
filename,
options: opts(),
code: `import b from '../server/server.ts';`,
errors: [{ messageId: 'pathRestrictedNoMessage' }],
},
{
filename,
options: opts(),
code: `import b from '../server/server.js';`,
errors: [{ messageId: 'pathRestrictedNoMessage' }],
},
{
filename,
options: opts(true),
code: `import b from '../server/server.ts';`,
errors: [{ messageId: 'pathRestrictedWithMessage' }],
},
],
});

35
.oxlint/rules/noThen.mjs Normal file
View File

@ -0,0 +1,35 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { ESLintUtils } from '@typescript-eslint/utils';
import { isPropertyAccess } from './utils/astUtils.mjs';
export const noThen = ESLintUtils.RuleCreator.withoutDocs({
name: 'no-then',
meta: {
type: 'problem',
messages: {
preferAwait: 'Prefer await instead of .then()',
},
schema: [],
defaultOptions: [],
},
create(context) {
return {
MemberExpression(node) {
if (!isPropertyAccess(node, 'then')) {
return;
}
if (node.parent.type !== 'CallExpression') {
return;
}
context.report({
node: node.property,
messageId: 'preferAwait',
});
},
};
},
});

View File

@ -0,0 +1,14 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
/**
* @param condition {unknown}
* @param message {string}
* @returns {asserts condition}
*/
export function assert(condition, message) {
if (condition == null || condition === false) {
throw new TypeError(message);
}
}

View File

@ -0,0 +1,36 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
/**
* @typedef {import("@typescript-eslint/utils").TSESTree.Node} Node
* @typedef {import("@typescript-eslint/utils").TSESTree.Literal} Literal
* @typedef {import("@typescript-eslint/utils").TSESTree.StringLiteral} StringLiteral
* @typedef {import("@typescript-eslint/utils").TSESTree.Identifier} Identifier
* @typedef {import("@typescript-eslint/utils").TSESTree.MemberExpression} MemberExpression
*/
/**
* @param {Node=} node
* @returns {node is StringLiteral}
*/
export function isStringLiteral(node) {
return node?.type === 'Literal' && typeof node.value === 'string';
}
/**
* @param {Node | null | undefined} node
* @param {string} property
* @returns {node is MemberExpression}
*/
export function isPropertyAccess(node, property) {
if (node?.type !== 'MemberExpression') {
return false;
}
if (node.computed) {
return node.property.type === 'Literal' && node.property.value === property;
}
return node.property.type === 'Identifier' && node.property.name === property;
}

View File

@ -0,0 +1,123 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import { getReferenceType } from './getReferenceType.mjs';
import { isStringLiteral } from './astUtils.mjs';
/**
* @typedef {import("@typescript-eslint/utils").TSESTree.StringLiteral} StringLiteral
* @typedef {import("@typescript-eslint/utils").TSESLint.SourceCode} SourceCode
* @typedef {import("@typescript-eslint/utils").TSESLint.RuleListener} RuleListener
*/
/**
* @param {SourceCode} sourceCode
* @param {(source: StringLiteral) => void} visitSource
* @returns {RuleListener}
*/
export function createImportSourceVisitor(sourceCode, visitSource) {
return {
// import ... from '<source>'
ImportDeclaration(node) {
visitSource(node.source);
},
// import('<source>')
ImportExpression(node) {
if (!isStringLiteral(node.source)) {
return;
}
visitSource(node.source);
},
CallExpression(node) {
// require('<source>')
if (node.callee.type === 'Identifier') {
if (node.callee.name !== 'require') {
return;
}
const refType = getReferenceType(sourceCode, node.callee);
if (refType != null && refType !== 'global') {
return;
}
const arg = node.arguments.at(0);
if (!isStringLiteral(arg)) {
return;
}
visitSource(arg);
return;
}
// require.resolve('<source>')
if (node.callee.type === 'MemberExpression') {
const { object, property } = node.callee;
if (object.type !== 'Identifier') {
return;
}
if (object.name !== 'require') {
return;
}
const refType = getReferenceType(sourceCode, object);
if (refType != null && refType !== 'global') {
return;
}
if (property.type !== 'Identifier') {
return;
}
if (property.name !== 'resolve') {
return;
}
const arg = node.arguments.at(0);
if (!isStringLiteral(arg)) {
return;
}
visitSource(arg);
}
},
// import.meta.resolve('<source>')
MetaProperty(node) {
if (node.meta.name !== 'import') {
return;
}
if (node.property.name !== 'meta') {
return;
}
const memberExpression = node.parent;
if (memberExpression.type !== 'MemberExpression') {
return;
}
if (memberExpression.property.type !== 'Identifier') {
return;
}
if (memberExpression.property.name !== 'resolve') {
return;
}
const callExpression = memberExpression.parent;
if (callExpression.type !== 'CallExpression') {
return;
}
const arg = callExpression.arguments.at(0);
if (!isStringLiteral(arg)) {
return;
}
visitSource(arg);
},
// export {...} from '<source>'
ExportNamedDeclaration(node) {
if (node.source == null) {
return;
}
visitSource(node.source);
},
// export * ... from '<source>'
ExportAllDeclaration(node) {
visitSource(node.source);
},
};
}

View File

@ -0,0 +1,18 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
/**
* @typedef {import("@typescript-eslint/utils").TSESTree.Identifier} Identifier
* @typedef {import("@typescript-eslint/utils").TSESLint.SourceCode} SourceCode
*/
/**
* @param {SourceCode} sourceCode
* @param {Identifier} node
*/
export function getReferenceType(sourceCode, node) {
const scope = sourceCode.getScope(node);
const ref = scope.references.find(r => r.identifier === node);
return ref?.resolved?.scope.type ?? null;
}

7
.oxlint/test-setup.mjs Normal file
View File

@ -0,0 +1,7 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import * as mocha from 'mocha'
import { RuleTester } from '@typescript-eslint/rule-tester';
RuleTester.afterAll = mocha.after;

1902
.oxlintrc.json Normal file

File diff suppressed because it is too large Load Diff

269
.pnpmfile.mjs Normal file
View File

@ -0,0 +1,269 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
//
// WARNING: Do not import (or even `import()`) any packages, they won't always be installed.
//
import { execSync } from 'node:child_process';
import { styleText } from 'node:util';
import { test } from 'node:test';
import assert from 'node:assert/strict';
/**
* From @pnpm/pnpmfile/lib/Hooks.d.ts
*
* @typedef {{
* deprecated?: boolean;
* }} PackageSnapshot
*
* @typedef {Record<string, PackageSnapshot>} PackageSnapshots
*
* @typedef {{
* packages?: PackageSnapshots
* }} LockfileObject
*
* @typedef {{
* log: (message: string) => void;
* }} HookContext
*
* @typedef {{
* verifyDepsBeforeRun?: unknown,
* }} Config
*
* @typedef {{
* afterAllResolved?: (
* lockfile: LockfileObject,
* context: HookContext,
* ) => LockfileObject | Promise<LockfileObject>;
* updateConfig?: (config: Config) => Config | Promise<Config>
* }} Hooks
*/
/**
* @param {boolean} condition
* @param {string} message
* @returns {asserts condition}
*/
// function assert(condition, message) {
// if (!condition) {
// throw new TypeError(message);
// }
// }
/**
* @param {string} message
*/
function formatError(message) {
return `${styleText(['bgRed', 'whiteBright'], '[ERROR]')} ${styleText('red', message)}`;
}
/** @type {any} */
let CACHED_WORKSPACE_CONFIG;
async function getWorkspaceConfig() {
if (CACHED_WORKSPACE_CONFIG == null) {
const stdout = execSync('pnpm config list --json', {
encoding: 'utf-8',
env: { PATH: process.env.PATH },
});
const config = JSON.parse(stdout);
CACHED_WORKSPACE_CONFIG = config;
}
return CACHED_WORKSPACE_CONFIG;
}
/**
* Samples:
* - "jest-process-manager@0.4.0"
* - "@jest/process-manager@0.4.0"
* - "jest-process-manager@0.4.0(debug@4.4.3)"
*
* @param {string} packagePath
*/
function parsePackagePath(packagePath) {
const truncateAt = packagePath.indexOf('(');
const packageSpec =
truncateAt === -1 ? packagePath : packagePath.slice(0, truncateAt);
const splitAt = packageSpec.lastIndexOf('@');
const name = packageSpec.slice(0, splitAt);
const version = packageSpec.slice(splitAt + 1);
return { name, version };
}
/**
* @typedef {{
* path: string,
* name: string,
* version: string,
* snapshot: PackageSnapshot,
* }} PackageSnapshotEntry
*/
/**
* @param {LockfileObject} lockfile
* @returns {ReadonlyArray<PackageSnapshotEntry>}
*/
function getPackages(lockfile) {
const { packages = {} } = lockfile;
return Object.keys(packages).map(path => {
const snapshot = packages[path];
const { name, version } = parsePackagePath(path);
return { path, name, version, snapshot };
});
}
/**
* Minimal semver support, only supports exact versions and `||`
* @param {string} version
* @param {string} range
*/
function satisfies(version, range) {
return range.split('||').some(choice => {
return choice.trim() === version;
});
}
/**
* @typedef {(lockfile: LockfileObject, context: HookContext) => Promise<boolean>} CustomCheck
*/
/** @type {CustomCheck} */
async function noDeprecatedPackages(lockfile, context) {
const config = await getWorkspaceConfig();
const packages = getPackages(lockfile);
const deprecated = packages.filter(pkg => {
if (!pkg.snapshot.deprecated) {
return false;
}
const allowed = config.allowedDeprecatedVersions?.[pkg.name];
if (allowed != null && satisfies(pkg.version, allowed)) {
return false;
}
return true;
});
const success = deprecated.length === 0;
if (!success) {
context.log('');
context.log(
formatError(
'Found deprecated packages, to ignore them add this to the pnpm-workspace.yaml file:'
)
);
context.log('');
context.log('allowedDeprecatedVersions:');
for (const pkg of deprecated) {
context.log(` '${pkg.name}': '${pkg.version}'`);
}
context.log('');
}
return success;
}
/** @type {ReadonlyArray<RegExp>} */
const RESTRICTED_DUPLICATE_DEPENDENCIES = [
// /^@signalapp\//,
// /^@indutny\//,
];
/**
* @param {string} name
* @returns {boolean}
*/
function isRestrictedDuplicateDependency(name) {
return RESTRICTED_DUPLICATE_DEPENDENCIES.some(regex => {
return regex.test(name);
});
}
/** @type {CustomCheck} */
async function restrictDuplicateDependencies(lockfile, context) {
const packages = getPackages(lockfile);
/** @type {Map<string, Set<string>>} */
const seen = new Map();
/** @type {Set<string>} */
const duplicates = new Set();
for (const pkg of packages) {
if (!isRestrictedDuplicateDependency(pkg.name)) {
continue;
}
let versions = seen.get(pkg.name);
if (versions != null) {
duplicates.add(pkg.name);
} else {
versions = new Set();
seen.set(pkg.name, versions);
}
versions.add(pkg.version);
}
const success = duplicates.size === 0;
if (!success) {
context.log('');
context.log(formatError('Found duplicate restricted packages:'));
context.log('');
for (const duplicate of duplicates) {
const versions = seen.get(duplicate);
assert(versions != null, `Missing package versions for ${duplicate}`);
context.log(` ${duplicate}: ${Array.from(versions).join(', ')}`);
}
context.log('');
}
return success;
}
/** @type {Hooks} */
export const hooks = {
async afterAllResolved(lockfile, context) {
const results = await Promise.all([
noDeprecatedPackages(lockfile, context),
restrictDuplicateDependencies(lockfile, context),
]);
const hasAnyFailures = results.includes(false);
if (hasAnyFailures) {
context.log(
formatError(
'pnpm install failed because of a custom check in .pnpmfile.mjs'
)
);
context.log('');
process.exit(1);
}
return lockfile;
},
updateConfig(config) {
return {
...config,
verifyDepsBeforeRun:
process.env.CI || process.env.SKIP_VERIFY_DEPS_BEFORE_RUN
? false
: config.verifyDepsBeforeRun,
};
},
};
if (process.env.NODE_TEST_CONTEXT) {
await test('noDeprecatedPackages', async () => {
const pkg = '@scope/pkg-name@1.0.0(@other-scope/other-pkg@2.0.0)';
const success = await noDeprecatedPackages(
{ packages: { [pkg]: { deprecated: true } } },
{ log: () => undefined }
);
assert(!success);
});
}

View File

@ -3,18 +3,14 @@
# Generated files
build/**/*.js
build/**/*.json
app/**/*.js
config/local-*.json
config/local.json
dist/**
js/components.js
js/util_worker.js
libtextsecure/components.js
libtextsecure/test/test.js
stylesheets/*.css
test/test.js
ts/**/*.js
!ts/**/.eslintrc.js
ts/protobuf/*.d.ts
ts/protobuf/*.js
stylesheets/manifest.css
@ -30,13 +26,13 @@ pnpm-lock.yaml
# Third-party files
node_modules/**
packages/*/node_modules/**
packages/lame/wrapper.mjs
packages/lame/lame-*/
packages/windows-ucv/dist/**
danger/node_modules/**
sticker-creator/node_modules/**
components/**
js/curve/**
js/Mp3LameEncoder.min.js
js/WebAudioRecorderMp3.js
js/calling-tools/**
scripts/emoji-datasource/emoji-datasource.json
# Assets
/images/

View File

@ -1,8 +1,9 @@
// Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
/** @type {import("prettier").Config} */
module.exports = {
const config = {
plugins: ['prettier-plugin-tailwindcss'],
singleQuote: true,
arrowParens: 'avoid',
@ -11,3 +12,5 @@ module.exports = {
tailwindFunctions: ['tw'],
tailwindAttributes: [],
};
export default config

View File

@ -1,7 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { Context } from 'react';
import type { ThemeType } from '../ts/types/Util.std.js';
export const StorybookThemeContext: Context<ThemeType>;

View File

@ -2,6 +2,6 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { createContext } from 'react';
import { ThemeType } from '../ts/types/Util.std.js';
import { ThemeType } from '../ts/types/Util.std.ts';
export const StorybookThemeContext = createContext(ThemeType.light);

View File

@ -11,7 +11,7 @@ const EXTERNALS = new Set(builtinModules);
EXTERNALS.delete('buffer');
EXTERNALS.delete('url');
const config: StorybookConfig = {
const storybookConfig: StorybookConfig = {
typescript: {
reactDocgen: false,
},
@ -43,63 +43,82 @@ const config: StorybookConfig = {
{ from: '../fonts', to: 'fonts' },
{ from: '../images', to: 'images' },
{ from: '../fixtures', to: 'fixtures' },
{
from: '../node_modules/emoji-datasource-apple/img',
to: 'node_modules/emoji-datasource-apple/img',
},
{
from: '../node_modules/intl-tel-input/build/img',
to: 'node_modules/intl-tel-input/build/img',
},
],
webpackFinal(config) {
config.cache = {
swc() {
return {
jsc: {
transform: {
react: { runtime: 'automatic' },
},
},
};
},
webpackFinal(webpackConfig) {
// oxlint-disable-next-line no-param-reassign
webpackConfig.cache = {
type: 'filesystem',
};
config.resolve!.extensionAlias = {
// oxlint-disable-next-line no-param-reassign, typescript/no-non-null-assertion
webpackConfig.resolve!.extensionAlias = {
'.js': ['.tsx', '.ts', '.js'],
};
config.module!.rules!.unshift({
// oxlint-disable-next-line typescript/no-non-null-assertion
webpackConfig.module!.rules!.unshift({
test: /\.scss$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader', options: { modules: false, url: false } },
{ loader: 'sass-loader' },
{ loader: require.resolve('style-loader') },
{
loader: require.resolve('css-loader'),
options: { modules: false, url: false },
},
{
loader: require.resolve('sass-loader'),
options: {
additionalData: '$is-storybook: true;',
},
},
],
});
config.module!.rules!.unshift({
// oxlint-disable-next-line typescript/no-non-null-assertion
webpackConfig.module!.rules!.unshift({
test: /\.css$/,
use: [
// prevent storybook defaults from being applied
],
});
config.module!.rules!.push({
// oxlint-disable-next-line typescript/no-non-null-assertion
webpackConfig.module!.rules!.push({
test: /tailwind-config\.css$/,
use: [
{
loader: 'postcss-loader',
loader: require.resolve('postcss-loader'),
options: {
postcssOptions: {
config: false,
plugins: {
'@tailwindcss/postcss': {},
},
plugins: [require.resolve('@tailwindcss/postcss')],
},
},
},
],
});
config.node = { global: true };
// oxlint-disable-next-line no-param-reassign
webpackConfig.node = { global: true };
config.externals = ({ request }, callback) => {
// oxlint-disable-next-line no-param-reassign
webpackConfig.externals = ({ request }, callback) => {
if (
(/^node:/.test(request) && request !== 'node:buffer') ||
(request.startsWith('node:') && request !== 'node:buffer') ||
EXTERNALS.has(request)
) {
// Keep Node.js imports unchanged
@ -108,16 +127,17 @@ const config: StorybookConfig = {
callback();
};
config.plugins!.push(
// oxlint-disable-next-line typescript/no-non-null-assertion
webpackConfig.plugins!.push(
new ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
})
);
return config;
return webpackConfig;
},
docs: {},
};
export default config;
export default storybookConfig;

View File

@ -3,38 +3,38 @@
import '../ts/window.d.ts';
import React, { StrictMode } from 'react';
import '@signalapp/quill-cjs/dist/quill.core.css';
import '../stylesheets/manifest.scss';
import '../stylesheets/tailwind-config.css';
import * as styles from './styles.scss';
import messages from '../_locales/en/messages.json';
import { Provider } from 'react-redux';
import { Store, combineReducers, createStore } from 'redux';
import type { Store } from 'redux';
import { combineReducers, createStore } from 'redux';
import { Globals } from '@react-spring/web';
import { StorybookThemeContext } from './StorybookThemeContext.std.js';
import { SystemThemeType, ThemeType } from '../ts/types/Util.std.js';
import { setupI18n } from '../ts/util/setupI18n.dom.js';
import { HourCyclePreference } from '../ts/types/I18N.std.js';
import { AxoProvider } from '../ts/axo/AxoProvider.dom.js';
import type { StateType } from '../ts/state/reducer.preload.js';
import { StorybookThemeContext } from './StorybookThemeContext.std.ts';
import { SystemThemeType, ThemeType } from '../ts/types/Util.std.ts';
import { setupI18n } from '../ts/util/setupI18n.dom.tsx';
import { HourCyclePreference } from '../ts/types/I18N.std.ts';
import { AppProvider } from '../ts/windows/AppProvider.dom.tsx';
import type { StateType } from '../ts/state/reducer.preload.ts';
import {
ScrollerLockContext,
createScrollerLock,
} from '../ts/hooks/useScrollLock.dom.js';
import { Environment, setEnvironment } from '../ts/environment.std.js';
import { parseUnknown } from '../ts/util/schemas.std.js';
import { LocaleEmojiListSchema } from '../ts/types/emoji.std.js';
import { FunProvider } from '../ts/components/fun/FunProvider.dom.js';
import { EmojiSkinTone } from '../ts/components/fun/data/emojis.std.js';
import { MOCK_GIFS_PAGINATED_ONE_PAGE } from '../ts/components/fun/mocks.dom.js';
import { NavTab } from '../ts/types/Nav.std.js';
} from '../ts/hooks/useScrollLock.dom.tsx';
import { Environment, setEnvironment } from '../ts/environment.std.ts';
import { parseUnknown } from '../ts/util/schemas.std.ts';
import { LocaleEmojiListSchema } from '../ts/types/emoji.std.ts';
import { FunProvider } from '../ts/components/fun/FunProvider.dom.tsx';
import { MOCK_GIFS_PAGINATED_ONE_PAGE } from '../ts/test-helpers/funPickerMocks.dom.tsx';
import { NavTab } from '../ts/types/Nav.std.ts';
import type { FunEmojiSelection } from '../ts/components/fun/panels/FunPanelEmojis.dom.js';
import type { FunGifSelection } from '../ts/components/fun/panels/FunPanelGifs.dom.js';
import type { FunStickerSelection } from '../ts/components/fun/panels/FunPanelStickers.dom.js';
import type { FunEmojiSelection } from '../ts/components/fun/panels/FunPanelEmojis.dom.tsx';
import type { FunGifSelection } from '../ts/components/fun/panels/FunPanelGifs.dom.tsx';
import type { FunStickerSelection } from '../ts/components/fun/panels/FunPanelStickers.dom.tsx';
import { Emoji } from '../ts/axo/emoji.std.ts';
setEnvironment(Environment.Development, true);
@ -100,10 +100,10 @@ const mockStore: Store<StateType> = createStore(
})
);
// eslint-disable-next-line
// oxlint-disable-next-line
const noop = () => {};
window.Whisper = window.Whisper || {};
window.Whisper ??= {};
window.Whisper.events = {
on: noop,
off: noop,
@ -138,6 +138,7 @@ window.SignalContext = {
platform: '',
release: '',
},
// oxlint-disable-next-line typescript/no-explicit-any
config: {} as any,
getHourCyclePreference: () => HourCyclePreference.UnknownPreference,
@ -166,7 +167,7 @@ window.SignalContext = {
_stopTrackingICUStrings: () => i18n.stopTrackingUsage(),
};
window.ConversationController = window.ConversationController || {};
window.ConversationController ??= {};
window.ConversationController.isSignalConversationId = () => false;
window.ConversationController.onConvoMessageMount = noop;
window.reduxStore = mockStore;
@ -182,14 +183,6 @@ window.Signal = {
},
};
function withStrictMode(Story, context) {
return (
<StrictMode>
<Story {...context} />
</StrictMode>
);
}
const withGlobalTypesProvider = (Story, context) => {
const theme =
context.globals.theme === 'light' ? ThemeType.light : ThemeType.dark;
@ -241,7 +234,7 @@ function withMockStoreProvider(Story, context) {
function withScrollLockProvider(Story, context) {
return (
<ScrollerLockContext.Provider
value={createScrollerLock('MockStories', () => {})}
value={createScrollerLock('MockStories', () => null)}
>
<Story {...context} />
</ScrollerLockContext.Provider>
@ -255,7 +248,7 @@ function withFunProvider(Story, context) {
recentEmojis={[]}
recentStickers={[]}
recentGifs={[]}
emojiSkinToneDefault={EmojiSkinTone.None}
emojiSkinToneDefault={Emoji.SkinTone.None}
onEmojiSkinToneDefaultChange={noop}
installedStickerPacks={[]}
showStickerPickerHint={false}
@ -265,12 +258,15 @@ function withFunProvider(Story, context) {
fetchGifsFeatured={() => Promise.resolve(MOCK_GIFS_PAGINATED_ONE_PAGE)}
fetchGif={() => Promise.resolve(new Blob([new Uint8Array(1)]))}
onSelectEmoji={function (emojiSelection: FunEmojiSelection): void {
// oxlint-disable-next-line no-console
console.log('onSelectEmoji', emojiSelection);
}}
onSelectSticker={function (stickerSelection: FunStickerSelection): void {
// oxlint-disable-next-line no-console
console.log('onSelectSticker', stickerSelection);
}}
onSelectGif={function (gifSelection: FunGifSelection): void {
// oxlint-disable-next-line no-console
console.log('onSelectGif', gifSelection);
}}
>
@ -279,19 +275,16 @@ function withFunProvider(Story, context) {
);
}
function withAxoProvider(Story, context) {
const globalValue = context.globals.direction ?? 'ltr';
const dir = globalValue === 'auto' ? 'ltr' : globalValue;
function withAppProvider(Story, context) {
return (
<AxoProvider dir={dir}>
<AppProvider>
<Story {...context} />
</AxoProvider>
</AppProvider>
);
}
export const decorators = [
withStrictMode,
withAxoProvider,
withAppProvider,
withGlobalTypesProvider,
withMockStoreProvider,
withScrollLockProvider,

View File

@ -54,16 +54,20 @@ const config: TestRunnerConfig = {
.or(page.getByTitle(value))
.or(page.getByLabel(value));
// oxlint-disable-next-line no-await-in-loop
if (await locator.count()) {
const first = locator.first();
try {
// oxlint-disable-next-line no-await-in-loop
await first.focus({ timeout: SECOND });
} catch {
// Opportunistic
}
try {
// oxlint-disable-next-line no-await-in-loop
if (await first.isVisible()) {
// oxlint-disable-next-line no-await-in-loop
await first.scrollIntoViewIfNeeded({ timeout: SECOND });
}
} catch {
@ -71,6 +75,7 @@ const config: TestRunnerConfig = {
}
}
// oxlint-disable-next-line no-await-in-loop
const image = await page.screenshot({
animations: 'disabled',
fullPage: true,

View File

@ -1,7 +1,12 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
// @ts-expect-error
import githubActionsFormatter from '@csstools/stylelint-formatter-github';
module.exports = {
/** @type {import('stylelint').Config} */
const config = {
formatter: process.env.CI ? githubActionsFormatter : undefined,
extends: [
'stylelint-config-recommended-scss',
'stylelint-config-css-modules',
@ -9,6 +14,7 @@ module.exports = {
plugins: ['stylelint-use-logical-spec'],
rules: {
// Disabled from recommended set to get stylelint working initially
'at-rule-empty-line-before': null,
'block-no-empty': null,
'declaration-block-no-duplicate-properties': null,
'declaration-block-no-shorthand-property-overrides': null,
@ -16,7 +22,6 @@ module.exports = {
'no-duplicate-selectors': null,
'no-descending-specificity': null,
'selector-pseudo-element-no-unknown': null,
'scss/at-import-partial-extension': null,
'scss/comment-no-empty': null,
'scss/no-global-function-names': null,
'scss/operator-no-newline-after': null,
@ -45,3 +50,5 @@ module.exports = {
},
},
};
export default config;

File diff suppressed because it is too large Load Diff

View File

@ -5,17 +5,49 @@
## Advice for new contributors
Start small. The PRs most likely to be merged are the ones that make small,
easily reviewed changes with clear and specific intentions. See below for more
[guidelines on pull requests](#pull-requests).
First, there are ways to contribute that don't involve the code at all. It helps to
_start small_. Here's a list of things to consider:
It's a good idea to gauge interest in your intended work by finding the current issue
for it or creating a new one yourself. You can use also that issue as a place to signal
your intentions and get feedback from the users most likely to appreciate your changes.
1. Talk about Signal with your friends and family - get them to join you in using it!
1. Join the Beta and test out recently-released features before the general public gets access
1. Find and comment on duplicate GitHub issues, so we can close them
1. Determine and provide workarounds on existing GitHub issues
1. Test Signal Desktop and find reliable, well-defined reproduction steps for existing GitHub issues
1. For a given GitHub issue, test Signal iOS and/or Signal Android to see if their behavior matches Signal Desktop, and provide the details of your analysis.
Once you've spent a little bit of time planning your solution, you can go
back to the issue and talk about your approach. We'd be happy to provide feedback. [An
ounce of prevention, as they say!](https://www.goodreads.com/quotes/247269-an-ounce-of-prevention-is-worth-a-pound-of-cure)
If you're ready to spend some time on a GitHub issue, please consider commenting to ask us
if there's interest. That will help ensure we minimize wasted time.
### Getting into the code
Have you spent some good time with Signal Desktop and GitHub issues? You can go deeper
and get into the code itself.
Again, it helps to start small. You don't need to create a PR to contribute!
1. Find the root cause of a GitHub issue, and provide your explanation in the issue, with links to specific places in the code
1. For a particularly difficult investigation, provide your process. Talk about the changes you made, and what happened - this might be useful to others working to fix the issue.
1. Test or review the code for others' pull requests
1. When reporting any issue, also include the specific place in the code where it happens.
### Considering a Pull Request?
If you're getting more comfortable with the code, you can consider assembling a PR. We
have very high standards for the code we put into Signal Desktop, so take special care
in changing the code, adding tests, and crafting the PR summary.
Because this can take a lot of time, it's a good idea to gauge interest in your intended
changes first. Find the current GitHub issue for it or create a new one yourself, then
post about your plans. That way you'll get feedback from the users most likely to
appreciate your changes. And we may tell you not to move forward with changes, because we
have other plans for the issue itself or that area of the code.
Than, once you've spent some time planning your solution, please consider going back
to the issue and talking about your approach. We'd be happy to provide feedback.
The PRs most likely to be merged are the ones that fix issues with real user impact,
make small easily reviewed changes, and have clear and specific intentions. See below for
more [guidelines on pull requests](#pull-requests).
## Developer Setup
@ -217,9 +249,19 @@ command line. You can run the client-side tests in an interactive session with
## Pull requests
So you wanna make a pull request? Please observe the following guidelines.
So you wanna make a pull request?
- First, make sure that your `pnpm run ready` run passes - it's very similar to what our
First, know that it's highly unlikely we'll accept visual changes, new strings, or really
anything that changes the user experience. Please talk to us first if you're planning
something like this! It's possible that we'll give you the okay, but very likely not.
The best changes fix bugs in our implementation of the existing user experience. For
example, it's almost certain that we'll reject anything that adds a new option.
More guidelines:
- Don't forget to sign the [CLA](https://signal.org/cla/).
- Be very sure that your `pnpm run ready` run passes - it's very similar to what our
Continuous Integration servers do to test the app.
- Please do not submit pull requests for translation fixes.
- Never use plain strings right in the source code - pull them from `messages.json`!
@ -230,7 +272,7 @@ So you wanna make a pull request? Please observe the following guidelines.
changes on the latest `main` branch, resolving any conflicts.
This ensures that your changes will merge cleanly when you open your PR.
- Be sure to add and run tests!
- Make sure the diff between the development branch and your branch contains only the
- Make sure the diff between the main branch and your branch contains only the
minimal set of changes needed to implement your feature or bugfix. This will
make it easier for the person reviewing your code to approve the changes.
Please do not submit a PR with commented out code or unfinished features.

3068
DATABASE_SCHEMA.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -20,14 +20,13 @@ Please search for any [existing issues](https://github.com/signalapp/Signal-Desk
Please use our community forum: https://community.signalusers.org/
## Contributing Code
## Contributing to the project
Please see [CONTRIBUTING.md](https://github.com/signalapp/Signal-Desktop/blob/main/CONTRIBUTING.md)
for setup instructions and guidelines for new contributors. Don't forget to sign the [CLA](https://signal.org/cla/).
Please see [CONTRIBUTING.md](https://github.com/signalapp/Signal-Desktop/blob/main/CONTRIBUTING.md). There are lots of ways to contribute - many that don't involve code!
## Contributing Funds
## Donate to Signal
You can donate to Signal development through the [Signal Technology Foundation](https://signal.org/donate), an independent 501c3 nonprofit.
You can donate to Signal from inside Signal apps (Desktop, Android, or iOS), or via the web here: [Signal Technology Foundation](https://signal.org/donate). Signal is an independent 501c3 nonprofit.
## Cryptography Notice

View File

@ -0,0 +1,21 @@
Signal is 'n boodskaptoepassing wat privaatheid as kernwaarde het. Dis gratis en maklik om te gebruik, met kragtige end-tot-end-enkriptering wat jou kletse en oproepe heeltemal privaat hou. Signal kan nie jou boodskappe lees of na jou oproepe luister nie, en niemand anders kan ook nie.
• Signal in MacOS is gekoppel aan Signal op jou foon.
• Stuur end-tot-end-geënkripteerde teksboodskappe, stemboodskappe, fotos, videos, GIFs en lêers gratis.
• Bly in verbinding met groepkletse vir tot 1000 deelnemers. Beheer wie boodskappe mag plaas en bestuur groeplede met administrateurtoestemming-instellings.
• Bel jou vriende met klokhelder end-tot-end-geënkripteerde stem- en video-oproepe. Groepoproepe ondersteun tot 75 deelnemers.
• Signal is gebou vir jou privaatheid. Ons weet glad nie wie jy is of met wie jy praat nie. Ons oopbron Signal-protokol beteken dat ons nie jou boodskappe kan lees of na jou oproepe kan luister nie. En niemand anders kan ook nie. Geen agterdeure nie, geen data-insameling nie, geen kompromisse nie.
• Deel foto-, teks- en video-Stories wat ná 24 uur verdwyn. Privaatheidinstellings hou jou in beheer van presies wie elke Storie kan sien.
• Signal is onafhanklik en niewinsgewend; dit is 'n ander soort tegnologie van 'n ander soort organisasie. As 'n 501c3-niewinsgewende organisasie word ons ondersteun deur jou skenkings, nie deur adverteerders of beleggers nie.
• Vir steundiens, vrae of meer inligting, besoek asb. https://support.signal.org/
Om na ons bronkode te kyk, besoek https://github.com/signalapp
Volg ons op X @signalapp en Instagram @signal_app

View File

@ -0,0 +1 @@
signal,boodskap(per),oproep,stem,geënkripteer,privaat,veilig,privaatheid,groep,video,klets,stories

View File

@ -0,0 +1 @@
Sê “hallo” vir privaatheid.

View File

@ -0,0 +1 @@
Signal Private Messenger

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
سيجنال هو تطبيق للمراسلة قائم على مراعاة الخصوصية. هو تطبيق مجاني وسهل الاستخدام، ويُوفِّر تشفيرًا قويًا من طرف لِطرف للحفاظ على السرية التامة لمراسلاتك. لا يمكن لسيجنال قراءة رسائلك أو التنصت على مكالماتك، ولا يُمكن لأي أحد آخر القيام بذلك أيضًا.
• يتصل سيجنال على نظام macOS بسيجنال المُثبَّت على هاتفك.
• أرسِل رسائل نصيّة ورسائل صوتية وصور وفيديوهات وصورة متحركة وملفات مُشفَّرة من طرف لطرف بالمجان.
• ابقَ على اتصال مع دردشات جماعية تجمع أكثر من 1000 شخص. تحكَّم في من يُمكنه النشر والقيام بإدارة أعضاء المجموعة باستخدام إعدادات أذونات المُشرِف.
• أجرِ مُكالمات صوتية ومكالمات فيديو مُشفَّرة بجودة عالية مع أصدقائك. تدعم المكالمات الجماعية ما يصل إلى 75 شخصًا.
صُمِّمَ سيجنال للحفاظ على خصوصيتك. لا نعرف شيئًا عنك أو مع من تتحدث. بروتوكول سيجنال الخاص بنا ذو المصدر المفتوح يعني أنه لا يُمكننا قراءة رسائلك أو الاستماع إلى مكالماتك. ولا يُمكن لأي شخص آخر القيام بذلك. لا أبواب خلفية ولا عملية جمع بيانات ولا مساومات.
• شارِك الصور والرسائل وقِصص الفيديو التي تختفي بعد 24 ساعة. تُتيح لك إعدادات الخصوصية فرصة البقاء مسؤولًا عن من يُمكنه رؤية كل قصة.
• تطبيق سيجنال مُستقل ولا يهدف إلى الربح؛ نوع مختلف من التكنولوجيا من نوع مختلف من المنظمات. وبصفتنا مؤسسة غير ربحية، فإننا نستمد دعمنا من التبرُّعات وليس من الإعلانات ولا المُستثمرين.
• للدعم أو لطرح الأسئلة أو للمزيد من المعلومات، يُرجى زيارة https://support.signal.org/
لإلقاء نظرة على كود المصدر الخاص بنا، قُم بزيارة https://github.com/signalapp
تابعنا على X على العنوان @signalapp وعلى انستغرام على العنوان @signal_app

View File

@ -0,0 +1 @@
سيجنال،رسالة،تطبيق،دردشة،مكالمة،صوت،مشفر،خاص،آمن،خصوصية،مجموعة،فيديو،دردشة،قصص

View File

@ -0,0 +1 @@
مرحبًا بكم في عالم الخصوصية.

View File

@ -0,0 +1 @@
سيجنال - تطبيق مراسلة يحترم الخصوصية

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
Signal məxfiliyə əsaslanan bir mesajlaşma tətbiqidir. Çatlar və zənglərinizin məxfiliyini tamamilə qoruyan güclü tam şifrələmə xüsusiyyəti ilə ondan istifadə pulsuz və asandır. Signal mesajlarınızı oxuya və ya zənglərinizi dinləyə bilmir, digərlərinə də bunu etməyə icazə vermir.
• MacOS ilə işləyən Signal telefonunuzdakı Signal tətbiqi ilə əlaqələndirilir.
• Tam şifrələnmiş mətnləri, səsli mesajları, fotoları, videoları, GIF və faylları pulsuz göndərin.
• 1000 nəfərə qədər iştirakçını dəstəkləyən qrup çatlarına qoşulun. Admin icazəsi parametrləri ilə kimin qrup üzvləri ilə yazı paylaşacağına və onları idarə edəcəyinə nəzarət edin.
• Dostlarınıza tam şifrələnmiş yüksək səs keyfiyyətinə malik audio və video zənglər edin. Qrup zəngləri 75 nəfərə qədər insanın qoşulmasını dəstəkləyir.
• Signal məxfiliyinizi qorumaq üçün yaradılıb. Siz və ya söhbət etdiyiniz şəxslər haqqında heç nə bilmirik. Açıq mənbəli Signal Protokolumuz mesajlarınızı oxuya və zənglərinizi dinləyə bilməyəcəyimizi nəzərdə tutur. Bunu nə biz, nə də başqası edə bilməz. Arxa qapılar, verilənlərin toplanması və kompromislər yoxdur.
• 24 saat sonra yox olacaq foto, mətn və video Hekayələri paylaşın. Məxfilik parametrləri sayəsində hər bir Hekayəni kimin görə biləcəyini dəqiqliklə seçirsiniz.
• Signal müstəqildir və mənfəət məqsədi güdmür, o, fərqli bir təşkilatın fərq yaradan bir texnologiyasıdır. Bir 501c3 qeyri-kommersiya təşkilatı kimi biz, reklamçı və investorların deyil, sizin ianələrinizlə dəstəklənirik.
• Dəstək, sual və əlavə məlumat üçün https://support.signal.org/ keçidinə daxil olun
Mənbə kodumuzu yoxlamaq üçün https://github.com/signalapp keçidinə daxil olun
Bizi X-də @signalapp, Instagram-da isə @signal_app istifadəçi profilləri ilə izləyin

View File

@ -0,0 +1 @@
signal,mesaj,messenger,zəng,səsli,şifrələnmiş,şəxsi,təhlükəsiz,məxfilik,qrup,video,çat,hekayələr

View File

@ -0,0 +1 @@
Məxfiliyə "Salam" verin.

View File

@ -0,0 +1 @@
Signal Gizli Mesajlaşma

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
Signal е приложение за съобщения, изградено около поверителността. То е безплатно и лесно за използване, със силно криптиране от край до край, което поддържа вашите чатове и обаждания напълно поверителни. Signal не може да чете вашите съобщения или да слуша вашите обаждания, а и никой друг не може.
• Signal на MacOS се свързва със Signal на телефона ви.
• Изпращайте криптирани от край до край текстови и гласови съобщения, снимки, видеоклипове, GIF-ове и файлове безплатно.
• Поддържайте връзка с групови чатове до 1000 души. Контролирайте кой може да публикува и управлява членовете на групата с настройките за администраторски разрешения.
• Обаждайте се на приятелите си с кристално чисти криптирани от край до край гласови и видео обаждания. Поддържат се групови разговори до 75 души.
• Signal е създаден за вашата поверителност. Не знаем нищо за вас или хората, с които говорите. Нашият Signal протокол с отворен код означава, че не можем да четем вашите съобщения или да слушаме вашите обаждания. И никой друг не може. Без задни вратички, без събиране на данни, без компромиси.
• Споделяйте снимки, текст и видео истории, които изчезват след 24 часа. Настройките за поверителност ви позволяват да контролирате точно кой може да вижда всяка история.
• Signal е независим и не е с цел печалба; различен вид технология от различен вид организация. Като организация с нестопанска цел, ние се издържаме от вашите дарения, а не от рекламодатели или инвеститори.
За поддръжка, въпроси или повече информация, моля посетете https://support.signal.org/
За да разгледате нашия изходен код, посетете https://github.com/signalapp
Последвайте ни в X @signalapp и Instagram @signal_app

View File

@ -0,0 +1 @@
signal,съобщения,обаждане,глас,криптиран,поверителен,сигурно,поверителност,група,видео,чат,истории

View File

@ -0,0 +1 @@
Поверителността преди всичко.

View File

@ -0,0 +1 @@
Signal - Private Messenger

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
Signal একটি মেসেজিং অ্যাপ যার মূল ভিত্তি হলো গোপনীয়তা। এটি ফ্রি-তে ব্যবহার করা যায় এবং ব্যবহার করা সহজ, এতে রয়েছে শক্তিশালী এন্ড-টু-এন্ড এনক্রিপশন ব্যবস্থা যা আপনার চ্যাট ও কল সম্পূর্ণ গোপন রাখে। Signal আপনার মেসেজ পড়তে বা আপনার কল শুনতে পারে না, এবং অন্য কেউই তা পারে না।
• MacOS-এর Signal আপনার ফোনের Signal-এর সাথে সংযুক্ত হয়।
• ফ্রি-তে এন্ড-টু-এন্ড এনক্রিপ্ট করা টেক্সট, ভয়েস মেসেজ, ছবি, ভিডিও, GIF ও ফাইল পাঠান।
• 1,000 জন পর্যন্ত ব্যক্তি নিয়ে সংগঠিত গ্রুপ চ্যাটের মাধ্যমে সংযুক্ত থাকুন সবার সাথে। অ্যাডমিন অনুমতির সেটিংস সহ গ্রুপ সদস্যদের মধ্যে কে কে পোস্ট করতে পারবেন এবং কে কে নিয়ন্ত্রণ করতে পারবেন তা নিয়ন্ত্রণ করুন।
• আপনার বন্ধুদের সাথে এন্ড-টু-এন্ড এনক্রিপ্ট করা ভয়েস ও ভিডিও কলের মাধ্যমে কল করুন। গ্ৰুপ কলে একসাথে সর্বোচ্চ 75 জন যোগ দিতে পারে।
• Signal আপনার গোপনীয়তা রক্ষা করার জন্য তৈরি করা হয়েছে। আমরা আপনার সম্পর্কে এবং আপনি কার সাথে কথা বলছেন তার সম্পর্কে কিছুই জানি না। আমাদের ওপেন সোর্স Signal Protocol-এর অর্থ হলো আমরা আপনার ম্যাসেজ পড়তে বা আপনার কল থেকে কথা শুনতে পারি না। এটি অন্য আর কেউও পারে না। নেই কোনো অসৎ উদ্দেশ্য, নেই কোনো তথ্য সংগ্রহের চর্চা, নেই কোনো আপোষ।
• ছবি, টেক্সট ও ভিডিও স্টোরি শেয়ার করা যায়, যা 24 ঘন্টা পরে অদৃশ্য হয়ে যায়। গোপনীয়তা সেটিংস আপনাকে প্রত্যেকটি স্টোরি কে দেখতে পাবেন তা নিয়ন্ত্রণ করার সুযোগ দেয়।
• Signal একটি স্বাধীন এবং অলাভজনক উদ্যোগ; একটি ভিন্নধর্মী প্রতিষ্ঠানের প্রচেষ্টায় তৈরি একটি ভিন্নধর্মী প্রযুক্তি। একটি 501c3 অলাভজনক প্রতিষ্ঠান হিসাবে আমরা আপনার দেওয়া ডোনেশনের সমর্থনে পরিচালিত, কোনো বিজ্ঞাপনদাতা বা বিনিয়োগকারীর দ্বারা সমর্থিত নয়।
• এ সংক্রান্ত সহায়তা, প্রশ্ন বা আরো তথ্যের জন্য অনুগ্রহ করে ভিজিট করুন: https://support.signal.org/
আমাদের সোর্স কোড চেক করতে, ভিজিট করুন: https://github.com/signalapp
X-এ @signalapp পেজে এবং Instagram-এ @signal_app পেজে আমাদের ফলো করুন

View File

@ -0,0 +1 @@
signal,মেসেজ,মেসেঞ্জার,কল,ভয়েস,এনক্রিপ্ট করা,ব্যক্তিগত,সুরক্ষিত,গোপনীয়তা,গ্ৰুপ,ভিডিও,চ্যাট,স্টোরি

View File

@ -0,0 +1 @@
গোপনীয়তাকে “হ্যালো” বলুন।

View File

@ -0,0 +1 @@
Signal - প্রাইভেট মেসেঞ্জার

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
Signal je aplikacija za razmjenu poruka čiji je ključ privatnost. Besplatna je i jednostavna za korištenje, sa snažnim sveobuhvatnim šifriranjem koje čuva vaše razgovore i pozive potpuno privatnima. Ne možemo čitati vaše poruke niti slušati vaše pozive, a ne može ni niko drugi.
• Signal na MacOS-u se povezuje sa Signalom na vašem telefonu.
• Besplatno šaljite tekstualne poruke, glasovne poruke, fotografije, videozapise, GIF-ove i datoteke, sve sveobuhvatno šifrirano.
• Ostanite povezani uz grupne razgovore i do 1000 ljudi. Kontrolirajte ko može objavljivati i upravljati članovima grupe pomoću postavki administratorskih dozvola.
• Pozovite svoje prijatelje kristalno čistim glasovnim i video pozivima, sve sveobhvatno šifrirano. Grupni pozivi podržavaju do 75 osoba.
• Signal je kreiran za vašu privatnost. Ne znamo ništa o vama niti s kim razgovarate. Naš Signal protokol otvorenog koda podrazumijeva da ne možemo čitati vaše poruke niti slušati vaše pozive. A to ne može ni niko drugi. Nema skrivenih motiva, prikupljanja podataka niti kompromisa.
• Dijelite fotografije, tekstualne i video priče koje nestaju nakon 24 sata. S postavkama privatnosti kontrolirate ko tačno može vidjeti svaku vašu priču.
• Signal je nezavisan i neprofitan; drugačija vrsta tehnologije od drugačije vrste organizacije. Kao neprofitna organizacija, podržavaju nas vaše donacije, a ne oglašivači ili investitori.
• Za podršku, pitanja ili više informacija posjetite https://support.signal.org/
Da provjerite naš izvorni kȏd, posjetite https://github.com/signalapp
Pratite nas na X @signalapp i Instagramu @signal_app

View File

@ -0,0 +1 @@
signal,poruka,messenger,poziv,glas,šifrirano,privatno,sigurno,privatnost,grupa,video,chat,priče

View File

@ -0,0 +1 @@
Uživajte u svojoj privatnosti.

View File

@ -0,0 +1 @@
Signal - Privatna aplikacija za razmjenu poruka

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
• Signal és una aplicació de missatgeria centrada en la privacitat. És gratuïta i fàcil d'utilitzar, amb un fort xifratge d'extrem a extrem que manté els teus xats i trucades completament privats. Ni Signal ni ningú més pot llegir els teus missatges o escoltar les teves trucades.
• El Signal de macOS s'enllaça amb el Signal del teu telèfon.
• Envia de manera gratuïta missatges, notes de veu, fotos, vídeos, GIFs i arxius xifrats d'extrem a extrem.
• No perdis mai el contacte amb xats grupals de fins a 1.000 persones. Controla qui pot publicar i gestionar els membres del grup amb la configuració de permisos d'administrador.
• Comunica't amb els teus amics amb trucades de veu i videotrucades totalment xifrades i nítides. S'admeten trucades en grup per a un màxim de 75 persones.
• Signal està dissenyada per a la teva privacitat. No sabem res de tu ni amb qui estàs parlant. El nostre protocol de codi obert de Signal significa que no podem llegir els teus missatges ni escoltar les teves trucades. Ni tampoc pot fer-ho ningú més. Sense portes posteriors, sense recollida de dades, sense compromisos.
• Comparteix Històries d'imatge, text i vídeo que desapareixeran automàticament al cap de 24 hores. La configuració de privacitat et manté a càrrec de qui pot veure exactament cada història.
• Signal és independent i sense ànim de lucre; un tipus de tecnologia diferent d'un tipus d'organització diferent. Com a entitat sense ànim de lucre 501c3, comptem amb el suport de les teves donacions, no d'anunciants o inversors.
• Si necessites assistència, tens alguna pregunta o vols saber-ne més, visita https://support.signal.org/
Per consultar el nostre codi font, visita https://github.com/signalapp
Segueix-nos a X @signalapp i Instagram @signal_app

View File

@ -0,0 +1 @@
signal,missatge,messenger,trucada,veu,xifrat,privat,segur,privacitat,grup,vídeo,xat,històries

View File

@ -0,0 +1 @@
Digues "hola" a la privacitat.

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