Fetch PNI group credentials
This commit is contained in:
parent
b9ba732724
commit
a450e13a99
61 changed files with 1911 additions and 875 deletions
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
@ -207,3 +207,11 @@ jobs:
|
||||||
env:
|
env:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
DEBUG: mock:test:*
|
DEBUG: mock:test:*
|
||||||
|
ARTIFACTS_DIR: artifacts/startup
|
||||||
|
|
||||||
|
- name: Upload mock server test logs on failure
|
||||||
|
if: failure()
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: logs
|
||||||
|
path: artifacts
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"directoryV2PublicKey": null,
|
"directoryV2PublicKey": null,
|
||||||
"directoryV2CodeHashes": null,
|
"directoryV2CodeHashes": null,
|
||||||
"directoryV3Url": "https://cdsi.staging.signal.org",
|
"directoryV3Url": "https://cdsi.staging.signal.org",
|
||||||
"directoryV3MRENCLAVE": "51133fecb3fa18aaf0c8f64cb763656d3272d9faaacdb26ae7df082e414fb142",
|
"directoryV3MRENCLAVE": "e5eaa62da3514e8b37ccabddb87e52e7f319ccf5120a13f9e1b42b87ec9dd3dd",
|
||||||
"directoryV3Root": "-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n",
|
"directoryV3Root": "-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n",
|
||||||
"cdn": {
|
"cdn": {
|
||||||
"0": "https://cdn-staging.signal.org",
|
"0": "https://cdn-staging.signal.org",
|
||||||
|
@ -26,6 +26,6 @@
|
||||||
"buildCreation": 0,
|
"buildCreation": 0,
|
||||||
"buildExpiration": 0,
|
"buildExpiration": 0,
|
||||||
"certificateAuthority": "-----BEGIN CERTIFICATE-----\nMIIF2zCCA8OgAwIBAgIUAMHz4g60cIDBpPr1gyZ/JDaaPpcwDQYJKoZIhvcNAQEL\nBQAwdTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT\nDU1vdW50YWluIFZpZXcxHjAcBgNVBAoTFVNpZ25hbCBNZXNzZW5nZXIsIExMQzEZ\nMBcGA1UEAxMQU2lnbmFsIE1lc3NlbmdlcjAeFw0yMjAxMjYwMDQ1NTFaFw0zMjAx\nMjQwMDQ1NTBaMHUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw\nFAYDVQQHEw1Nb3VudGFpbiBWaWV3MR4wHAYDVQQKExVTaWduYWwgTWVzc2VuZ2Vy\nLCBMTEMxGTAXBgNVBAMTEFNpZ25hbCBNZXNzZW5nZXIwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQDEecifxMHHlDhxbERVdErOhGsLO08PUdNkATjZ1kT5\n1uPf5JPiRbus9F4J/GgBQ4ANSAjIDZuFY0WOvG/i0qvxthpW70ocp8IjkiWTNiA8\n1zQNQdCiWbGDU4B1sLi2o4JgJMweSkQFiyDynqWgHpw+KmvytCzRWnvrrptIfE4G\nPxNOsAtXFbVH++8JO42IaKRVlbfpe/lUHbjiYmIpQroZPGPY4Oql8KM3o39ObPnT\no1WoM4moyOOZpU3lV1awftvWBx1sbTBL02sQWfHRxgNVF+Pj0fdDMMFdFJobArrL\nVfK2Ua+dYN4pV5XIxzVarSRW73CXqQ+2qloPW/ynpa3gRtYeGWV4jl7eD0PmeHpK\nOY78idP4H1jfAv0TAVeKpuB5ZFZ2szcySxrQa8d7FIf0kNJe9gIRjbQ+XrvnN+ZZ\nvj6d+8uBJq8LfQaFhlVfI0/aIdggScapR7w8oLpvdflUWqcTLeXVNLVrg15cEDwd\nlV8PVscT/KT0bfNzKI80qBq8LyRmauAqP0CDjayYGb2UAabnhefgmRY6aBE5mXxd\nbyAEzzCS3vDxjeTD8v8nbDq+SD6lJi0i7jgwEfNDhe9XK50baK15Udc8Cr/ZlhGM\njNmWqBd0jIpaZm1rzWA0k4VwXtDwpBXSz8oBFshiXs3FD6jHY2IhOR3ppbyd4qRU\npwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\nHQ4EFgQUtfNLxuXWS9DlgGuMUMNnW7yx83EwHwYDVR0jBBgwFoAUtfNLxuXWS9Dl\ngGuMUMNnW7yx83EwDQYJKoZIhvcNAQELBQADggIBABUeiryS0qjykBN75aoHO9bV\nPrrX+DSJIB9V2YzkFVyh/io65QJMG8naWVGOSpVRwUwhZVKh3JVp/miPgzTGAo7z\nhrDIoXc+ih7orAMb19qol/2Ha8OZLa75LojJNRbZoCR5C+gM8C+spMLjFf9k3JVx\ndajhtRUcR0zYhwsBS7qZ5Me0d6gRXD0ZiSbadMMxSw6KfKk3ePmPb9gX+MRTS63c\n8mLzVYB/3fe/bkpq4RUwzUHvoZf+SUD7NzSQRQQMfvAHlxk11TVNxScYPtxXDyiy\n3Cssl9gWrrWqQ/omuHipoH62J7h8KAYbr6oEIq+Czuenc3eCIBGBBfvCpuFOgckA\nXXE4MlBasEU0MO66GrTCgMt9bAmSw3TrRP12+ZUFxYNtqWluRU8JWQ4FCCPcz9pg\nMRBOgn4lTxDZG+I47OKNuSRjFEP94cdgxd3H/5BK7WHUz1tAGQ4BgepSXgmjzifF\nT5FVTDTl3ZnWUVBXiHYtbOBgLiSIkbqGMCLtrBtFIeQ7RRTb3L+IE9R0UB0cJB3A\nXbf1lVkOcmrdu2h8A32aCwtr5S1fBF1unlG7imPmqJfpOMWa8yIF/KWVm29JAPq8\nLrsybb0z5gg8w7ZblEuB9zOW9M3l60DXuJO6l7g+deV6P96rv2unHS8UlvWiVWDy\n9qfgAJizyy3kqM4lOwBH\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIID7zCCAtegAwIBAgIJAIm6LatK5PNiMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j\naXNjbzEdMBsGA1UECgwUT3BlbiBXaGlzcGVyIFN5c3RlbXMxHTAbBgNVBAsMFE9w\nZW4gV2hpc3BlciBTeXN0ZW1zMRMwEQYDVQQDDApUZXh0U2VjdXJlMB4XDTEzMDMy\nNTIyMTgzNVoXDTIzMDMyMzIyMTgzNVowgY0xCzAJBgNVBAYTAlVTMRMwEQYDVQQI\nDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMR0wGwYDVQQKDBRP\ncGVuIFdoaXNwZXIgU3lzdGVtczEdMBsGA1UECwwUT3BlbiBXaGlzcGVyIFN5c3Rl\nbXMxEzARBgNVBAMMClRleHRTZWN1cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\nggEKAoIBAQDBSWBpOCBDF0i4q2d4jAXkSXUGpbeWugVPQCjaL6qD9QDOxeW1afvf\nPo863i6Crq1KDxHpB36EwzVcjwLkFTIMeo7t9s1FQolAt3mErV2U0vie6Ves+yj6\ngrSfxwIDAcdsKmI0a1SQCZlr3Q1tcHAkAKFRxYNawADyps5B+Zmqcgf653TXS5/0\nIPPQLocLn8GWLwOYNnYfBvILKDMItmZTtEbucdigxEA9mfIvvHADEbteLtVgwBm9\nR5vVvtwrD6CCxI3pgH7EH7kMP0Od93wLisvn1yhHY7FuYlrkYqdkMvWUrKoASVw4\njb69vaeJCUdU+HCoXOSP1PQcL6WenNCHAgMBAAGjUDBOMB0GA1UdDgQWBBQBixjx\nP/s5GURuhYa+lGUypzI8kDAfBgNVHSMEGDAWgBQBixjxP/s5GURuhYa+lGUypzI8\nkDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQB+Hr4hC56m0LvJAu1R\nK6NuPDbTMEN7/jMojFHxH4P3XPFfupjR+bkDq0pPOU6JjIxnrD1XD/EVmTTaTVY5\niOheyv7UzJOefb2pLOc9qsuvI4fnaESh9bhzln+LXxtCrRPGhkxA1IMIo3J/s2WF\n/KVYZyciu6b4ubJ91XPAuBNZwImug7/srWvbpk0hq6A6z140WTVSKtJG7EP41kJe\n/oF4usY5J7LPkxK3LWzMJnb5EIJDmRvyH8pyRwWg6Qm6qiGFaI4nL8QU4La1x2en\n4DGXRaLMPRwjELNgQPodR38zoCMuA8gHZfZYYoZ7D7Q1wNUiVHcxuFrEeBaYJbLE\nrwLV\n-----END CERTIFICATE-----\n",
|
"certificateAuthority": "-----BEGIN CERTIFICATE-----\nMIIF2zCCA8OgAwIBAgIUAMHz4g60cIDBpPr1gyZ/JDaaPpcwDQYJKoZIhvcNAQEL\nBQAwdTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT\nDU1vdW50YWluIFZpZXcxHjAcBgNVBAoTFVNpZ25hbCBNZXNzZW5nZXIsIExMQzEZ\nMBcGA1UEAxMQU2lnbmFsIE1lc3NlbmdlcjAeFw0yMjAxMjYwMDQ1NTFaFw0zMjAx\nMjQwMDQ1NTBaMHUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw\nFAYDVQQHEw1Nb3VudGFpbiBWaWV3MR4wHAYDVQQKExVTaWduYWwgTWVzc2VuZ2Vy\nLCBMTEMxGTAXBgNVBAMTEFNpZ25hbCBNZXNzZW5nZXIwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQDEecifxMHHlDhxbERVdErOhGsLO08PUdNkATjZ1kT5\n1uPf5JPiRbus9F4J/GgBQ4ANSAjIDZuFY0WOvG/i0qvxthpW70ocp8IjkiWTNiA8\n1zQNQdCiWbGDU4B1sLi2o4JgJMweSkQFiyDynqWgHpw+KmvytCzRWnvrrptIfE4G\nPxNOsAtXFbVH++8JO42IaKRVlbfpe/lUHbjiYmIpQroZPGPY4Oql8KM3o39ObPnT\no1WoM4moyOOZpU3lV1awftvWBx1sbTBL02sQWfHRxgNVF+Pj0fdDMMFdFJobArrL\nVfK2Ua+dYN4pV5XIxzVarSRW73CXqQ+2qloPW/ynpa3gRtYeGWV4jl7eD0PmeHpK\nOY78idP4H1jfAv0TAVeKpuB5ZFZ2szcySxrQa8d7FIf0kNJe9gIRjbQ+XrvnN+ZZ\nvj6d+8uBJq8LfQaFhlVfI0/aIdggScapR7w8oLpvdflUWqcTLeXVNLVrg15cEDwd\nlV8PVscT/KT0bfNzKI80qBq8LyRmauAqP0CDjayYGb2UAabnhefgmRY6aBE5mXxd\nbyAEzzCS3vDxjeTD8v8nbDq+SD6lJi0i7jgwEfNDhe9XK50baK15Udc8Cr/ZlhGM\njNmWqBd0jIpaZm1rzWA0k4VwXtDwpBXSz8oBFshiXs3FD6jHY2IhOR3ppbyd4qRU\npwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\nHQ4EFgQUtfNLxuXWS9DlgGuMUMNnW7yx83EwHwYDVR0jBBgwFoAUtfNLxuXWS9Dl\ngGuMUMNnW7yx83EwDQYJKoZIhvcNAQELBQADggIBABUeiryS0qjykBN75aoHO9bV\nPrrX+DSJIB9V2YzkFVyh/io65QJMG8naWVGOSpVRwUwhZVKh3JVp/miPgzTGAo7z\nhrDIoXc+ih7orAMb19qol/2Ha8OZLa75LojJNRbZoCR5C+gM8C+spMLjFf9k3JVx\ndajhtRUcR0zYhwsBS7qZ5Me0d6gRXD0ZiSbadMMxSw6KfKk3ePmPb9gX+MRTS63c\n8mLzVYB/3fe/bkpq4RUwzUHvoZf+SUD7NzSQRQQMfvAHlxk11TVNxScYPtxXDyiy\n3Cssl9gWrrWqQ/omuHipoH62J7h8KAYbr6oEIq+Czuenc3eCIBGBBfvCpuFOgckA\nXXE4MlBasEU0MO66GrTCgMt9bAmSw3TrRP12+ZUFxYNtqWluRU8JWQ4FCCPcz9pg\nMRBOgn4lTxDZG+I47OKNuSRjFEP94cdgxd3H/5BK7WHUz1tAGQ4BgepSXgmjzifF\nT5FVTDTl3ZnWUVBXiHYtbOBgLiSIkbqGMCLtrBtFIeQ7RRTb3L+IE9R0UB0cJB3A\nXbf1lVkOcmrdu2h8A32aCwtr5S1fBF1unlG7imPmqJfpOMWa8yIF/KWVm29JAPq8\nLrsybb0z5gg8w7ZblEuB9zOW9M3l60DXuJO6l7g+deV6P96rv2unHS8UlvWiVWDy\n9qfgAJizyy3kqM4lOwBH\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIID7zCCAtegAwIBAgIJAIm6LatK5PNiMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j\naXNjbzEdMBsGA1UECgwUT3BlbiBXaGlzcGVyIFN5c3RlbXMxHTAbBgNVBAsMFE9w\nZW4gV2hpc3BlciBTeXN0ZW1zMRMwEQYDVQQDDApUZXh0U2VjdXJlMB4XDTEzMDMy\nNTIyMTgzNVoXDTIzMDMyMzIyMTgzNVowgY0xCzAJBgNVBAYTAlVTMRMwEQYDVQQI\nDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMR0wGwYDVQQKDBRP\ncGVuIFdoaXNwZXIgU3lzdGVtczEdMBsGA1UECwwUT3BlbiBXaGlzcGVyIFN5c3Rl\nbXMxEzARBgNVBAMMClRleHRTZWN1cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\nggEKAoIBAQDBSWBpOCBDF0i4q2d4jAXkSXUGpbeWugVPQCjaL6qD9QDOxeW1afvf\nPo863i6Crq1KDxHpB36EwzVcjwLkFTIMeo7t9s1FQolAt3mErV2U0vie6Ves+yj6\ngrSfxwIDAcdsKmI0a1SQCZlr3Q1tcHAkAKFRxYNawADyps5B+Zmqcgf653TXS5/0\nIPPQLocLn8GWLwOYNnYfBvILKDMItmZTtEbucdigxEA9mfIvvHADEbteLtVgwBm9\nR5vVvtwrD6CCxI3pgH7EH7kMP0Od93wLisvn1yhHY7FuYlrkYqdkMvWUrKoASVw4\njb69vaeJCUdU+HCoXOSP1PQcL6WenNCHAgMBAAGjUDBOMB0GA1UdDgQWBBQBixjx\nP/s5GURuhYa+lGUypzI8kDAfBgNVHSMEGDAWgBQBixjxP/s5GURuhYa+lGUypzI8\nkDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQB+Hr4hC56m0LvJAu1R\nK6NuPDbTMEN7/jMojFHxH4P3XPFfupjR+bkDq0pPOU6JjIxnrD1XD/EVmTTaTVY5\niOheyv7UzJOefb2pLOc9qsuvI4fnaESh9bhzln+LXxtCrRPGhkxA1IMIo3J/s2WF\n/KVYZyciu6b4ubJ91XPAuBNZwImug7/srWvbpk0hq6A6z140WTVSKtJG7EP41kJe\n/oF4usY5J7LPkxK3LWzMJnb5EIJDmRvyH8pyRwWg6Qm6qiGFaI4nL8QU4La1x2en\n4DGXRaLMPRwjELNgQPodR38zoCMuA8gHZfZYYoZ7D7Q1wNUiVHcxuFrEeBaYJbLE\nrwLV\n-----END CERTIFICATE-----\n",
|
||||||
"serverPublicParams": "ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXQ==",
|
"serverPublicParams": "ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUj",
|
||||||
"serverTrustRoot": "BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx"
|
"serverTrustRoot": "BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"0": "https://cdn.signal.org",
|
"0": "https://cdn.signal.org",
|
||||||
"2": "https://cdn2.signal.org"
|
"2": "https://cdn2.signal.org"
|
||||||
},
|
},
|
||||||
"serverPublicParams": "AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXQ==",
|
"serverPublicParams": "AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P",
|
||||||
"serverTrustRoot": "BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF",
|
"serverTrustRoot": "BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF",
|
||||||
"updatesEnabled": true
|
"updatesEnabled": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
"@indutny/frameless-titlebar": "2.3.4",
|
"@indutny/frameless-titlebar": "2.3.4",
|
||||||
"@popperjs/core": "2.9.2",
|
"@popperjs/core": "2.9.2",
|
||||||
"@react-spring/web": "9.4.5",
|
"@react-spring/web": "9.4.5",
|
||||||
"@signalapp/libsignal-client": "0.17.0",
|
"@signalapp/libsignal-client": "0.18.1",
|
||||||
"@sindresorhus/is": "0.8.0",
|
"@sindresorhus/is": "0.8.0",
|
||||||
"@types/fabric": "4.5.3",
|
"@types/fabric": "4.5.3",
|
||||||
"abort-controller": "3.0.0",
|
"abort-controller": "3.0.0",
|
||||||
|
@ -190,7 +190,7 @@
|
||||||
"@babel/preset-typescript": "7.17.12",
|
"@babel/preset-typescript": "7.17.12",
|
||||||
"@electron/fuses": "1.5.0",
|
"@electron/fuses": "1.5.0",
|
||||||
"@mixer/parallel-prettier": "2.0.1",
|
"@mixer/parallel-prettier": "2.0.1",
|
||||||
"@signalapp/mock-server": "1.5.1",
|
"@signalapp/mock-server": "2.0.1",
|
||||||
"@storybook/addon-a11y": "6.5.6",
|
"@storybook/addon-a11y": "6.5.6",
|
||||||
"@storybook/addon-actions": "6.5.6",
|
"@storybook/addon-actions": "6.5.6",
|
||||||
"@storybook/addon-controls": "6.5.6",
|
"@storybook/addon-controls": "6.5.6",
|
||||||
|
|
|
@ -101,6 +101,8 @@ message GroupChange {
|
||||||
|
|
||||||
message ModifyMemberProfileKeyAction {
|
message ModifyMemberProfileKeyAction {
|
||||||
bytes presentation = 1;
|
bytes presentation = 1;
|
||||||
|
bytes user_id = 2;
|
||||||
|
bytes profile_key = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddMemberPendingProfileKeyAction {
|
message AddMemberPendingProfileKeyAction {
|
||||||
|
@ -113,6 +115,15 @@ message GroupChange {
|
||||||
|
|
||||||
message PromoteMemberPendingProfileKeyAction {
|
message PromoteMemberPendingProfileKeyAction {
|
||||||
bytes presentation = 1;
|
bytes presentation = 1;
|
||||||
|
bytes user_id = 2;
|
||||||
|
bytes profile_key = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PromoteMemberPendingPniAciProfileKeyAction {
|
||||||
|
bytes presentation = 1;
|
||||||
|
bytes user_id = 2;
|
||||||
|
bytes pni = 3;
|
||||||
|
bytes profile_key = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddMemberPendingAdminApprovalAction {
|
message AddMemberPendingAdminApprovalAction {
|
||||||
|
@ -200,7 +211,8 @@ message GroupChange {
|
||||||
ModifyAnnouncementsOnlyAction modifyAnnouncementsOnly = 21; // change epoch = 3
|
ModifyAnnouncementsOnlyAction modifyAnnouncementsOnly = 21; // change epoch = 3
|
||||||
repeated AddMemberBannedAction addMembersBanned = 22; // change epoch = 4
|
repeated AddMemberBannedAction addMembersBanned = 22; // change epoch = 4
|
||||||
repeated DeleteMemberBannedAction deleteMembersBanned = 23; // change epoch = 4
|
repeated DeleteMemberBannedAction deleteMembersBanned = 23; // change epoch = 4
|
||||||
// next: 24
|
repeated PromoteMemberPendingPniAciProfileKeyAction promoteMembersPendingPniAciProfileKey = 24; // change epoch = 5
|
||||||
|
// next: 25
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes actions = 1; // The serialized actions
|
bytes actions = 1; // The serialized actions
|
||||||
|
|
|
@ -51,7 +51,7 @@ import {
|
||||||
import { senderCertificateService } from './services/senderCertificate';
|
import { senderCertificateService } from './services/senderCertificate';
|
||||||
import { GROUP_CREDENTIALS_KEY } from './services/groupCredentialFetcher';
|
import { GROUP_CREDENTIALS_KEY } from './services/groupCredentialFetcher';
|
||||||
import * as KeyboardLayout from './services/keyboardLayout';
|
import * as KeyboardLayout from './services/keyboardLayout';
|
||||||
import { routineProfileRefresh } from './routineProfileRefresh';
|
import { RoutineProfileRefresher } from './routineProfileRefresh';
|
||||||
import { isMoreRecentThan, isOlderThan, toDayMillis } from './util/timestamp';
|
import { isMoreRecentThan, isOlderThan, toDayMillis } from './util/timestamp';
|
||||||
import { isValidReactionEmoji } from './reactions/isValidReactionEmoji';
|
import { isValidReactionEmoji } from './reactions/isValidReactionEmoji';
|
||||||
import type { ConversationModel } from './models/conversations';
|
import type { ConversationModel } from './models/conversations';
|
||||||
|
@ -220,6 +220,7 @@ export async function startApp(): Promise<void> {
|
||||||
let server: WebAPIType | undefined;
|
let server: WebAPIType | undefined;
|
||||||
let messageReceiver: MessageReceiver | undefined;
|
let messageReceiver: MessageReceiver | undefined;
|
||||||
let challengeHandler: ChallengeHandler | undefined;
|
let challengeHandler: ChallengeHandler | undefined;
|
||||||
|
let routineProfileRefresher: RoutineProfileRefresher | undefined;
|
||||||
|
|
||||||
window.storage.onready(() => {
|
window.storage.onready(() => {
|
||||||
server = window.WebAPI.connect(
|
server = window.WebAPI.connect(
|
||||||
|
@ -812,6 +813,11 @@ export async function startApp(): Promise<void> {
|
||||||
await window.Signal.Data.clearAllErrorStickerPackAttempts();
|
await window.Signal.Data.clearAllErrorStickerPackAttempts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (window.isBeforeVersion(lastVersion, 'v5.50.0-alpha.1')) {
|
||||||
|
await window.storage.put('groupCredentials', []);
|
||||||
|
await window.Signal.Data.removeAllProfileKeyCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
// This one should always be last - it could restart the app
|
// This one should always be last - it could restart the app
|
||||||
if (window.isBeforeVersion(lastVersion, 'v5.30.0-alpha')) {
|
if (window.isBeforeVersion(lastVersion, 'v5.30.0-alpha')) {
|
||||||
await deleteAllLogs();
|
await deleteAllLogs();
|
||||||
|
@ -1172,7 +1178,12 @@ export async function startApp(): Promise<void> {
|
||||||
window.Whisper.events.on('userChanged', (reconnect = false) => {
|
window.Whisper.events.on('userChanged', (reconnect = false) => {
|
||||||
const newDeviceId = window.textsecure.storage.user.getDeviceId();
|
const newDeviceId = window.textsecure.storage.user.getDeviceId();
|
||||||
const newNumber = window.textsecure.storage.user.getNumber();
|
const newNumber = window.textsecure.storage.user.getNumber();
|
||||||
const newUuid = window.textsecure.storage.user.getUuid()?.toString();
|
const newACI = window.textsecure.storage.user
|
||||||
|
.getUuid(UUIDKind.ACI)
|
||||||
|
?.toString();
|
||||||
|
const newPNI = window.textsecure.storage.user
|
||||||
|
.getUuid(UUIDKind.PNI)
|
||||||
|
?.toString();
|
||||||
const ourConversation =
|
const ourConversation =
|
||||||
window.ConversationController.getOurConversation();
|
window.ConversationController.getOurConversation();
|
||||||
|
|
||||||
|
@ -1184,7 +1195,8 @@ export async function startApp(): Promise<void> {
|
||||||
ourConversationId: ourConversation?.get('id'),
|
ourConversationId: ourConversation?.get('id'),
|
||||||
ourDeviceId: newDeviceId,
|
ourDeviceId: newDeviceId,
|
||||||
ourNumber: newNumber,
|
ourNumber: newNumber,
|
||||||
ourUuid: newUuid,
|
ourACI: newACI,
|
||||||
|
ourPNI: newPNI,
|
||||||
regionCode: window.storage.get('regionCode'),
|
regionCode: window.storage.get('regionCode'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2492,14 +2504,15 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
// Kick off a profile refresh if necessary, but don't wait for it, as failure is
|
// Kick off a profile refresh if necessary, but don't wait for it, as failure is
|
||||||
// tolerable.
|
// tolerable.
|
||||||
const ourConversationId =
|
if (!routineProfileRefresher) {
|
||||||
window.ConversationController.getOurConversationId();
|
routineProfileRefresher = new RoutineProfileRefresher({
|
||||||
if (ourConversationId) {
|
getAllConversations: () => window.ConversationController.getAll(),
|
||||||
routineProfileRefresh({
|
getOurConversationId: () =>
|
||||||
allConversations: window.ConversationController.getAll(),
|
window.ConversationController.getOurConversationId(),
|
||||||
ourConversationId,
|
|
||||||
storage,
|
storage,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
routineProfileRefresher.start();
|
||||||
} else {
|
} else {
|
||||||
assert(
|
assert(
|
||||||
false,
|
false,
|
||||||
|
@ -2625,10 +2638,14 @@ export async function startApp(): Promise<void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ourACI = window.textsecure.storage.user.getUuid(UUIDKind.ACI);
|
||||||
|
const ourPNI = window.textsecure.storage.user.getUuid(UUIDKind.PNI);
|
||||||
|
|
||||||
// We drop typing notifications in groups we're not a part of
|
// We drop typing notifications in groups we're not a part of
|
||||||
if (
|
if (
|
||||||
!isDirectConversation(conversation.attributes) &&
|
!isDirectConversation(conversation.attributes) &&
|
||||||
!conversation.hasMember(ourId)
|
!(ourACI && conversation.hasMember(ourACI)) &&
|
||||||
|
!(ourPNI && conversation.hasMember(ourPNI))
|
||||||
) {
|
) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`Received typing indicator for group ${conversation.idForLogging()}, which we're not a part of. Dropping.`
|
`Received typing indicator for group ${conversation.idForLogging()}, which we're not a part of. Dropping.`
|
||||||
|
|
|
@ -16,7 +16,8 @@ import type { FullJSXType } from '../Intl';
|
||||||
|
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
const OUR_ID = UUID.generate().toString();
|
const OUR_ACI = UUID.generate().toString();
|
||||||
|
const OUR_PNI = UUID.generate().toString();
|
||||||
const CONTACT_A = UUID.generate().toString();
|
const CONTACT_A = UUID.generate().toString();
|
||||||
const CONTACT_B = UUID.generate().toString();
|
const CONTACT_B = UUID.generate().toString();
|
||||||
const CONTACT_C = UUID.generate().toString();
|
const CONTACT_C = UUID.generate().toString();
|
||||||
|
@ -59,7 +60,8 @@ const renderChange = (
|
||||||
groupMemberships={groupMemberships}
|
groupMemberships={groupMemberships}
|
||||||
groupName={groupName}
|
groupName={groupName}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
ourUuid={OUR_ID}
|
ourACI={OUR_ACI}
|
||||||
|
ourPNI={OUR_PNI}
|
||||||
renderContact={renderContact}
|
renderContact={renderContact}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -89,7 +91,11 @@ export const Multiple = (): JSX.Element => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'member-add',
|
type: 'member-add',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'member-add',
|
||||||
|
uuid: OUR_PNI,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'description',
|
type: 'description',
|
||||||
|
@ -97,7 +103,7 @@ export const Multiple = (): JSX.Element => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'member-privilege',
|
type: 'member-privilege',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
newPrivilege: RoleEnum.ADMINISTRATOR,
|
newPrivilege: RoleEnum.ADMINISTRATOR,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -110,7 +116,7 @@ export const Create = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'create',
|
type: 'create',
|
||||||
|
@ -140,7 +146,7 @@ export const Title = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'title',
|
type: 'title',
|
||||||
|
@ -166,7 +172,7 @@ export const Title = (): JSX.Element => {
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'title',
|
type: 'title',
|
||||||
|
@ -196,7 +202,7 @@ export const Avatar = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'avatar',
|
type: 'avatar',
|
||||||
|
@ -222,7 +228,7 @@ export const Avatar = (): JSX.Element => {
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'avatar',
|
type: 'avatar',
|
||||||
|
@ -255,7 +261,7 @@ export const AccessAttributes = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'access-attributes',
|
type: 'access-attributes',
|
||||||
|
@ -281,7 +287,7 @@ export const AccessAttributes = (): JSX.Element => {
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'access-attributes',
|
type: 'access-attributes',
|
||||||
|
@ -318,7 +324,7 @@ export const AccessMembers = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'access-members',
|
type: 'access-members',
|
||||||
|
@ -344,7 +350,7 @@ export const AccessMembers = (): JSX.Element => {
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'access-members',
|
type: 'access-members',
|
||||||
|
@ -381,7 +387,7 @@ export const AccessInviteLink = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'access-invite-link',
|
type: 'access-invite-link',
|
||||||
|
@ -407,7 +413,7 @@ export const AccessInviteLink = (): JSX.Element => {
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'access-invite-link',
|
type: 'access-invite-link',
|
||||||
|
@ -444,11 +450,11 @@ export const MemberAdd = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-add',
|
type: 'member-add',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -457,7 +463,7 @@ export const MemberAdd = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-add',
|
type: 'member-add',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -465,12 +471,12 @@ export const MemberAdd = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-add',
|
type: 'member-add',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-add',
|
type: 'member-add',
|
||||||
|
@ -508,7 +514,7 @@ export const MemberAddFromInvited = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-add-from-invite',
|
type: 'member-add-from-invite',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
inviter: CONTACT_B,
|
inviter: CONTACT_B,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -517,14 +523,14 @@ export const MemberAddFromInvited = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-add-from-invite',
|
type: 'member-add-from-invite',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
inviter: CONTACT_A,
|
inviter: CONTACT_A,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{/* the rest of the 'someone added someone else' checks */}
|
{/* the rest of the 'someone added someone else' checks */}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-add-from-invite',
|
type: 'member-add-from-invite',
|
||||||
|
@ -554,21 +560,21 @@ export const MemberAddFromInvited = (): JSX.Element => {
|
||||||
})}
|
})}
|
||||||
{/* in all of these we know the user has accepted the invite */}
|
{/* in all of these we know the user has accepted the invite */}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-add-from-invite',
|
type: 'member-add-from-invite',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
inviter: CONTACT_A,
|
inviter: CONTACT_A,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-add-from-invite',
|
type: 'member-add-from-invite',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -578,7 +584,7 @@ export const MemberAddFromInvited = (): JSX.Element => {
|
||||||
{
|
{
|
||||||
type: 'member-add-from-invite',
|
type: 'member-add-from-invite',
|
||||||
uuid: CONTACT_A,
|
uuid: CONTACT_A,
|
||||||
inviter: OUR_ID,
|
inviter: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -601,6 +607,17 @@ export const MemberAddFromInvited = (): JSX.Element => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
ACI accepts PNI invite:
|
||||||
|
{renderChange({
|
||||||
|
from: OUR_PNI,
|
||||||
|
details: [
|
||||||
|
{
|
||||||
|
type: 'member-add-from-invite',
|
||||||
|
uuid: OUR_ACI,
|
||||||
|
inviter: CONTACT_B,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -613,11 +630,11 @@ export const MemberAddFromLink = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-add-from-link',
|
type: 'member-add-from-link',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -654,7 +671,7 @@ export const MemberAddFromAdminApproval = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-add-from-admin-approval',
|
type: 'member-add-from-admin-approval',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -662,12 +679,12 @@ export const MemberAddFromAdminApproval = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-add-from-admin-approval',
|
type: 'member-add-from-admin-approval',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-add-from-admin-approval',
|
type: 'member-add-from-admin-approval',
|
||||||
|
@ -704,11 +721,11 @@ export const MemberRemove = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-remove',
|
type: 'member-remove',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -717,7 +734,7 @@ export const MemberRemove = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-remove',
|
type: 'member-remove',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -725,12 +742,12 @@ export const MemberRemove = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-remove',
|
type: 'member-remove',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-remove',
|
type: 'member-remove',
|
||||||
|
@ -776,7 +793,7 @@ export const MemberPrivilege = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-privilege',
|
type: 'member-privilege',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
newPrivilege: RoleEnum.ADMINISTRATOR,
|
newPrivilege: RoleEnum.ADMINISTRATOR,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -785,13 +802,13 @@ export const MemberPrivilege = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-privilege',
|
type: 'member-privilege',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
newPrivilege: RoleEnum.ADMINISTRATOR,
|
newPrivilege: RoleEnum.ADMINISTRATOR,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-privilege',
|
type: 'member-privilege',
|
||||||
|
@ -824,7 +841,7 @@ export const MemberPrivilege = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-privilege',
|
type: 'member-privilege',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
newPrivilege: RoleEnum.DEFAULT,
|
newPrivilege: RoleEnum.DEFAULT,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -833,13 +850,13 @@ export const MemberPrivilege = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-privilege',
|
type: 'member-privilege',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
newPrivilege: RoleEnum.DEFAULT,
|
newPrivilege: RoleEnum.DEFAULT,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'member-privilege',
|
type: 'member-privilege',
|
||||||
|
@ -879,7 +896,7 @@ export const PendingAddOne = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'pending-add-one',
|
type: 'pending-add-one',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -887,12 +904,12 @@ export const PendingAddOne = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'pending-add-one',
|
type: 'pending-add-one',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'pending-add-one',
|
type: 'pending-add-one',
|
||||||
|
@ -929,7 +946,7 @@ export const PendingAddMany = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'pending-add-many',
|
type: 'pending-add-many',
|
||||||
|
@ -971,17 +988,17 @@ export const PendingRemoveOne = (): JSX.Element => {
|
||||||
{
|
{
|
||||||
type: 'pending-remove-one',
|
type: 'pending-remove-one',
|
||||||
uuid: INVITEE_A,
|
uuid: INVITEE_A,
|
||||||
inviter: OUR_ID,
|
inviter: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'pending-remove-one',
|
type: 'pending-remove-one',
|
||||||
uuid: INVITEE_A,
|
uuid: INVITEE_A,
|
||||||
inviter: OUR_ID,
|
inviter: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -991,7 +1008,7 @@ export const PendingRemoveOne = (): JSX.Element => {
|
||||||
{
|
{
|
||||||
type: 'pending-remove-one',
|
type: 'pending-remove-one',
|
||||||
uuid: INVITEE_A,
|
uuid: INVITEE_A,
|
||||||
inviter: OUR_ID,
|
inviter: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -1000,7 +1017,7 @@ export const PendingRemoveOne = (): JSX.Element => {
|
||||||
{
|
{
|
||||||
type: 'pending-remove-one',
|
type: 'pending-remove-one',
|
||||||
uuid: INVITEE_A,
|
uuid: INVITEE_A,
|
||||||
inviter: OUR_ID,
|
inviter: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -1029,7 +1046,7 @@ export const PendingRemoveOne = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'pending-remove-one',
|
type: 'pending-remove-one',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
inviter: CONTACT_B,
|
inviter: CONTACT_B,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1056,7 +1073,7 @@ export const PendingRemoveOne = (): JSX.Element => {
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'pending-remove-one',
|
type: 'pending-remove-one',
|
||||||
|
@ -1076,7 +1093,7 @@ export const PendingRemoveOne = (): JSX.Element => {
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'pending-remove-one',
|
type: 'pending-remove-one',
|
||||||
|
@ -1113,12 +1130,12 @@ export const PendingRemoveMany = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'pending-remove-many',
|
type: 'pending-remove-many',
|
||||||
count: 5,
|
count: 5,
|
||||||
inviter: OUR_ID,
|
inviter: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -1128,7 +1145,7 @@ export const PendingRemoveMany = (): JSX.Element => {
|
||||||
{
|
{
|
||||||
type: 'pending-remove-many',
|
type: 'pending-remove-many',
|
||||||
count: 5,
|
count: 5,
|
||||||
inviter: OUR_ID,
|
inviter: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -1137,12 +1154,12 @@ export const PendingRemoveMany = (): JSX.Element => {
|
||||||
{
|
{
|
||||||
type: 'pending-remove-many',
|
type: 'pending-remove-many',
|
||||||
count: 5,
|
count: 5,
|
||||||
inviter: OUR_ID,
|
inviter: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'pending-remove-many',
|
type: 'pending-remove-many',
|
||||||
|
@ -1171,7 +1188,7 @@ export const PendingRemoveMany = (): JSX.Element => {
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'pending-remove-many',
|
type: 'pending-remove-many',
|
||||||
|
@ -1212,7 +1229,7 @@ export const AdminApprovalAdd = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'admin-approval-add-one',
|
type: 'admin-approval-add-one',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -1236,11 +1253,11 @@ export const AdminApprovalRemove = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'admin-approval-remove-one',
|
type: 'admin-approval-remove-one',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
|
@ -1248,12 +1265,12 @@ export const AdminApprovalRemove = (): JSX.Element => {
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'admin-approval-remove-one',
|
type: 'admin-approval-remove-one',
|
||||||
uuid: OUR_ID,
|
uuid: OUR_ACI,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'admin-approval-remove-one',
|
type: 'admin-approval-remove-one',
|
||||||
|
@ -1354,7 +1371,7 @@ export const GroupLinkAdd = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'group-link-add',
|
type: 'group-link-add',
|
||||||
|
@ -1380,7 +1397,7 @@ export const GroupLinkAdd = (): JSX.Element => {
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'group-link-add',
|
type: 'group-link-add',
|
||||||
|
@ -1417,7 +1434,7 @@ export const GroupLinkReset = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'group-link-reset',
|
type: 'group-link-reset',
|
||||||
|
@ -1451,7 +1468,7 @@ export const GroupLinkRemove = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'group-link-remove',
|
type: 'group-link-remove',
|
||||||
|
@ -1485,7 +1502,7 @@ export const DescriptionRemove = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
removed: true,
|
removed: true,
|
||||||
|
@ -1523,7 +1540,7 @@ export const DescriptionChange = (): JSX.Element => {
|
||||||
<>
|
<>
|
||||||
{renderChange(
|
{renderChange(
|
||||||
{
|
{
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'description',
|
type: 'description',
|
||||||
|
@ -1571,7 +1588,7 @@ export const AnnouncementGroupChange = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'announcements-only',
|
type: 'announcements-only',
|
||||||
|
@ -1597,7 +1614,7 @@ export const AnnouncementGroupChange = (): JSX.Element => {
|
||||||
],
|
],
|
||||||
})}
|
})}
|
||||||
{renderChange({
|
{renderChange({
|
||||||
from: OUR_ID,
|
from: OUR_ACI,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
type: 'announcements-only',
|
type: 'announcements-only',
|
||||||
|
|
|
@ -30,7 +30,8 @@ export type PropsDataType = {
|
||||||
}>;
|
}>;
|
||||||
groupBannedMemberships?: Array<UUIDStringType>;
|
groupBannedMemberships?: Array<UUIDStringType>;
|
||||||
groupName?: string;
|
groupName?: string;
|
||||||
ourUuid?: UUIDStringType;
|
ourACI?: UUIDStringType;
|
||||||
|
ourPNI?: UUIDStringType;
|
||||||
change: GroupV2ChangeType;
|
change: GroupV2ChangeType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -132,7 +133,8 @@ function GroupV2Detail({
|
||||||
groupBannedMemberships,
|
groupBannedMemberships,
|
||||||
groupName,
|
groupName,
|
||||||
i18n,
|
i18n,
|
||||||
ourUuid,
|
ourACI,
|
||||||
|
ourPNI,
|
||||||
renderContact,
|
renderContact,
|
||||||
text,
|
text,
|
||||||
}: {
|
}: {
|
||||||
|
@ -148,7 +150,8 @@ function GroupV2Detail({
|
||||||
groupName?: string;
|
groupName?: string;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
fromId?: UUIDStringType;
|
fromId?: UUIDStringType;
|
||||||
ourUuid?: UUIDStringType;
|
ourACI?: UUIDStringType;
|
||||||
|
ourPNI?: UUIDStringType;
|
||||||
renderContact: SmartContactRendererType<FullJSXType>;
|
renderContact: SmartContactRendererType<FullJSXType>;
|
||||||
text: FullJSXType;
|
text: FullJSXType;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
|
@ -241,7 +244,8 @@ function GroupV2Detail({
|
||||||
detail.type === 'admin-approval-bounce' &&
|
detail.type === 'admin-approval-bounce' &&
|
||||||
areWeAdmin &&
|
areWeAdmin &&
|
||||||
detail.uuid &&
|
detail.uuid &&
|
||||||
detail.uuid !== ourUuid &&
|
detail.uuid !== ourACI &&
|
||||||
|
detail.uuid !== ourPNI &&
|
||||||
(!fromId || fromId === detail.uuid) &&
|
(!fromId || fromId === detail.uuid) &&
|
||||||
!groupMemberships?.some(item => item.uuid === detail.uuid) &&
|
!groupMemberships?.some(item => item.uuid === detail.uuid) &&
|
||||||
!groupBannedMemberships?.some(uuid => uuid === detail.uuid)
|
!groupBannedMemberships?.some(uuid => uuid === detail.uuid)
|
||||||
|
@ -276,7 +280,8 @@ export function GroupV2Change(props: PropsType): ReactElement {
|
||||||
groupMemberships,
|
groupMemberships,
|
||||||
groupName,
|
groupName,
|
||||||
i18n,
|
i18n,
|
||||||
ourUuid,
|
ourACI,
|
||||||
|
ourPNI,
|
||||||
renderContact,
|
renderContact,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
@ -284,7 +289,8 @@ export function GroupV2Change(props: PropsType): ReactElement {
|
||||||
<>
|
<>
|
||||||
{renderChange<FullJSXType>(change, {
|
{renderChange<FullJSXType>(change, {
|
||||||
i18n,
|
i18n,
|
||||||
ourUuid,
|
ourACI,
|
||||||
|
ourPNI,
|
||||||
renderContact,
|
renderContact,
|
||||||
renderString: renderStringToIntl,
|
renderString: renderStringToIntl,
|
||||||
}).map(({ detail, isLastText, text }, index) => {
|
}).map(({ detail, isLastText, text }, index) => {
|
||||||
|
@ -302,7 +308,8 @@ export function GroupV2Change(props: PropsType): ReactElement {
|
||||||
// Difficult to find a unique key for this type
|
// Difficult to find a unique key for this type
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
key={index}
|
key={index}
|
||||||
ourUuid={ourUuid}
|
ourACI={ourACI}
|
||||||
|
ourPNI={ourPNI}
|
||||||
renderContact={renderContact}
|
renderContact={renderContact}
|
||||||
text={text}
|
text={text}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -115,9 +115,9 @@ export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
|
||||||
contact => contact.username === username
|
contact => contact.username === username
|
||||||
);
|
);
|
||||||
|
|
||||||
isUsernameVisible = candidateContacts.every(
|
isUsernameVisible =
|
||||||
contact => contact.username !== username
|
Boolean(username) &&
|
||||||
);
|
candidateContacts.every(contact => contact.username !== username);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputRef = useRef<null | HTMLInputElement>(null);
|
const inputRef = useRef<null | HTMLInputElement>(null);
|
||||||
|
|
|
@ -20,7 +20,8 @@ export type StringRendererType<T> = (
|
||||||
export type RenderOptionsType<T> = {
|
export type RenderOptionsType<T> = {
|
||||||
from?: UUIDStringType;
|
from?: UUIDStringType;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
ourUuid?: UUIDStringType;
|
ourACI?: UUIDStringType;
|
||||||
|
ourPNI?: UUIDStringType;
|
||||||
renderContact: SmartContactRendererType<T>;
|
renderContact: SmartContactRendererType<T>;
|
||||||
renderString: StringRendererType<T>;
|
renderString: StringRendererType<T>;
|
||||||
};
|
};
|
||||||
|
@ -66,8 +67,15 @@ export function renderChangeDetail<T>(
|
||||||
detail: GroupV2ChangeDetailType,
|
detail: GroupV2ChangeDetailType,
|
||||||
options: RenderOptionsType<T>
|
options: RenderOptionsType<T>
|
||||||
): T | string | ReadonlyArray<T | string> {
|
): T | string | ReadonlyArray<T | string> {
|
||||||
const { from, i18n, ourUuid, renderContact, renderString } = options;
|
const { from, i18n, ourACI, ourPNI, renderContact, renderString } = options;
|
||||||
const fromYou = Boolean(from && ourUuid && from === ourUuid);
|
|
||||||
|
const isOurUuid = (uuid?: UUIDStringType): boolean => {
|
||||||
|
if (!uuid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Boolean((ourACI && uuid === ourACI) || (ourPNI && uuid === ourPNI));
|
||||||
|
};
|
||||||
|
const fromYou = isOurUuid(from);
|
||||||
|
|
||||||
if (detail.type === 'create') {
|
if (detail.type === 'create') {
|
||||||
if (fromYou) {
|
if (fromYou) {
|
||||||
|
@ -229,7 +237,7 @@ export function renderChangeDetail<T>(
|
||||||
}
|
}
|
||||||
if (detail.type === 'member-add') {
|
if (detail.type === 'member-add') {
|
||||||
const { uuid } = detail;
|
const { uuid } = detail;
|
||||||
const weAreJoiner = Boolean(ourUuid && uuid === ourUuid);
|
const weAreJoiner = isOurUuid(uuid);
|
||||||
|
|
||||||
if (weAreJoiner) {
|
if (weAreJoiner) {
|
||||||
if (fromYou) {
|
if (fromYou) {
|
||||||
|
@ -259,10 +267,11 @@ export function renderChangeDetail<T>(
|
||||||
}
|
}
|
||||||
if (detail.type === 'member-add-from-invite') {
|
if (detail.type === 'member-add-from-invite') {
|
||||||
const { uuid, inviter } = detail;
|
const { uuid, inviter } = detail;
|
||||||
const weAreJoiner = Boolean(ourUuid && uuid === ourUuid);
|
const weAreJoiner = isOurUuid(uuid);
|
||||||
const weAreInviter = Boolean(inviter && ourUuid && inviter === ourUuid);
|
const weAreInviter = isOurUuid(inviter);
|
||||||
|
const pniPromotedToACI = weAreJoiner && from === ourPNI;
|
||||||
|
|
||||||
if (!from || from !== uuid) {
|
if (!from || (from !== uuid && !pniPromotedToACI)) {
|
||||||
if (weAreJoiner) {
|
if (weAreJoiner) {
|
||||||
// They can't be the same, no fromYou check here
|
// They can't be the same, no fromYou check here
|
||||||
if (from) {
|
if (from) {
|
||||||
|
@ -322,7 +331,7 @@ export function renderChangeDetail<T>(
|
||||||
if (detail.type === 'member-add-from-link') {
|
if (detail.type === 'member-add-from-link') {
|
||||||
const { uuid } = detail;
|
const { uuid } = detail;
|
||||||
|
|
||||||
if (fromYou && ourUuid && uuid === ourUuid) {
|
if (fromYou && isOurUuid(uuid)) {
|
||||||
return renderString('GroupV2--member-add-from-link--you--you', i18n);
|
return renderString('GroupV2--member-add-from-link--you--you', i18n);
|
||||||
}
|
}
|
||||||
if (from && uuid === from) {
|
if (from && uuid === from) {
|
||||||
|
@ -340,7 +349,7 @@ export function renderChangeDetail<T>(
|
||||||
}
|
}
|
||||||
if (detail.type === 'member-add-from-admin-approval') {
|
if (detail.type === 'member-add-from-admin-approval') {
|
||||||
const { uuid } = detail;
|
const { uuid } = detail;
|
||||||
const weAreJoiner = Boolean(ourUuid && uuid === ourUuid);
|
const weAreJoiner = isOurUuid(uuid);
|
||||||
|
|
||||||
if (weAreJoiner) {
|
if (weAreJoiner) {
|
||||||
if (from) {
|
if (from) {
|
||||||
|
@ -391,7 +400,7 @@ export function renderChangeDetail<T>(
|
||||||
}
|
}
|
||||||
if (detail.type === 'member-remove') {
|
if (detail.type === 'member-remove') {
|
||||||
const { uuid } = detail;
|
const { uuid } = detail;
|
||||||
const weAreLeaver = Boolean(ourUuid && uuid === ourUuid);
|
const weAreLeaver = isOurUuid(uuid);
|
||||||
|
|
||||||
if (weAreLeaver) {
|
if (weAreLeaver) {
|
||||||
if (fromYou) {
|
if (fromYou) {
|
||||||
|
@ -427,7 +436,7 @@ export function renderChangeDetail<T>(
|
||||||
}
|
}
|
||||||
if (detail.type === 'member-privilege') {
|
if (detail.type === 'member-privilege') {
|
||||||
const { uuid, newPrivilege } = detail;
|
const { uuid, newPrivilege } = detail;
|
||||||
const weAreMember = Boolean(ourUuid && uuid === ourUuid);
|
const weAreMember = isOurUuid(uuid);
|
||||||
|
|
||||||
if (newPrivilege === RoleEnum.ADMINISTRATOR) {
|
if (newPrivilege === RoleEnum.ADMINISTRATOR) {
|
||||||
if (weAreMember) {
|
if (weAreMember) {
|
||||||
|
@ -513,7 +522,7 @@ export function renderChangeDetail<T>(
|
||||||
}
|
}
|
||||||
if (detail.type === 'pending-add-one') {
|
if (detail.type === 'pending-add-one') {
|
||||||
const { uuid } = detail;
|
const { uuid } = detail;
|
||||||
const weAreInvited = Boolean(ourUuid && uuid === ourUuid);
|
const weAreInvited = isOurUuid(uuid);
|
||||||
if (weAreInvited) {
|
if (weAreInvited) {
|
||||||
if (from) {
|
if (from) {
|
||||||
return renderString('GroupV2--pending-add--one--you--other', i18n, [
|
return renderString('GroupV2--pending-add--one--you--other', i18n, [
|
||||||
|
@ -554,8 +563,8 @@ export function renderChangeDetail<T>(
|
||||||
}
|
}
|
||||||
if (detail.type === 'pending-remove-one') {
|
if (detail.type === 'pending-remove-one') {
|
||||||
const { inviter, uuid } = detail;
|
const { inviter, uuid } = detail;
|
||||||
const weAreInviter = Boolean(inviter && ourUuid && inviter === ourUuid);
|
const weAreInviter = isOurUuid(inviter);
|
||||||
const weAreInvited = Boolean(ourUuid && uuid === ourUuid);
|
const weAreInvited = isOurUuid(uuid);
|
||||||
const sentByInvited = Boolean(from && from === uuid);
|
const sentByInvited = Boolean(from && from === uuid);
|
||||||
const sentByInviter = Boolean(from && inviter && from === inviter);
|
const sentByInviter = Boolean(from && inviter && from === inviter);
|
||||||
|
|
||||||
|
@ -649,7 +658,7 @@ export function renderChangeDetail<T>(
|
||||||
}
|
}
|
||||||
if (detail.type === 'pending-remove-many') {
|
if (detail.type === 'pending-remove-many') {
|
||||||
const { count, inviter } = detail;
|
const { count, inviter } = detail;
|
||||||
const weAreInviter = Boolean(inviter && ourUuid && inviter === ourUuid);
|
const weAreInviter = isOurUuid(inviter);
|
||||||
|
|
||||||
if (weAreInviter) {
|
if (weAreInviter) {
|
||||||
if (fromYou) {
|
if (fromYou) {
|
||||||
|
@ -729,7 +738,7 @@ export function renderChangeDetail<T>(
|
||||||
}
|
}
|
||||||
if (detail.type === 'admin-approval-add-one') {
|
if (detail.type === 'admin-approval-add-one') {
|
||||||
const { uuid } = detail;
|
const { uuid } = detail;
|
||||||
const weAreJoiner = Boolean(ourUuid && uuid === ourUuid);
|
const weAreJoiner = isOurUuid(uuid);
|
||||||
|
|
||||||
if (weAreJoiner) {
|
if (weAreJoiner) {
|
||||||
return renderString('GroupV2--admin-approval-add-one--you', i18n);
|
return renderString('GroupV2--admin-approval-add-one--you', i18n);
|
||||||
|
@ -740,7 +749,7 @@ export function renderChangeDetail<T>(
|
||||||
}
|
}
|
||||||
if (detail.type === 'admin-approval-remove-one') {
|
if (detail.type === 'admin-approval-remove-one') {
|
||||||
const { uuid } = detail;
|
const { uuid } = detail;
|
||||||
const weAreJoiner = Boolean(ourUuid && uuid === ourUuid);
|
const weAreJoiner = isOurUuid(uuid);
|
||||||
|
|
||||||
if (weAreJoiner) {
|
if (weAreJoiner) {
|
||||||
if (fromYou) {
|
if (fromYou) {
|
||||||
|
|
613
ts/groups.ts
613
ts/groups.ts
File diff suppressed because it is too large
Load diff
|
@ -13,6 +13,7 @@ import {
|
||||||
parseGroupLink,
|
parseGroupLink,
|
||||||
} from '../groups';
|
} from '../groups';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
|
import { UUIDKind } from '../types/UUID';
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
import { longRunningTaskWrapper } from '../util/longRunningTaskWrapper';
|
import { longRunningTaskWrapper } from '../util/longRunningTaskWrapper';
|
||||||
import { isGroupV1 } from '../util/whatTypeOfConversation';
|
import { isGroupV1 } from '../util/whatTypeOfConversation';
|
||||||
|
@ -64,13 +65,9 @@ export async function joinViaLink(hash: string): Promise<void> {
|
||||||
const existingConversation =
|
const existingConversation =
|
||||||
window.ConversationController.get(id) ||
|
window.ConversationController.get(id) ||
|
||||||
window.ConversationController.getByDerivedGroupV2Id(id);
|
window.ConversationController.getByDerivedGroupV2Id(id);
|
||||||
const ourConversationId =
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid(UUIDKind.ACI);
|
||||||
window.ConversationController.getOurConversationIdOrThrow();
|
|
||||||
|
|
||||||
if (
|
if (existingConversation && existingConversation.hasMember(ourUuid)) {
|
||||||
existingConversation &&
|
|
||||||
existingConversation.hasMember(ourConversationId)
|
|
||||||
) {
|
|
||||||
log.warn(
|
log.warn(
|
||||||
`joinViaLink/${logId}: Already a member of group, opening conversation`
|
`joinViaLink/${logId}: Already a member of group, opening conversation`
|
||||||
);
|
);
|
||||||
|
@ -152,7 +149,7 @@ export async function joinViaLink(hash: string): Promise<void> {
|
||||||
if (
|
if (
|
||||||
approvalRequired &&
|
approvalRequired &&
|
||||||
existingConversation &&
|
existingConversation &&
|
||||||
existingConversation.isMemberAwaitingApproval(ourConversationId)
|
existingConversation.isMemberAwaitingApproval(ourUuid)
|
||||||
) {
|
) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`joinViaLink/${logId}: Already awaiting approval, opening conversation`
|
`joinViaLink/${logId}: Already awaiting approval, opening conversation`
|
||||||
|
@ -246,9 +243,9 @@ export async function joinViaLink(hash: string): Promise<void> {
|
||||||
// via some other process. If so, just open that conversation.
|
// via some other process. If so, just open that conversation.
|
||||||
if (
|
if (
|
||||||
targetConversation &&
|
targetConversation &&
|
||||||
(targetConversation.hasMember(ourConversationId) ||
|
(targetConversation.hasMember(ourUuid) ||
|
||||||
(approvalRequired &&
|
(approvalRequired &&
|
||||||
targetConversation.isMemberAwaitingApproval(ourConversationId)))
|
targetConversation.isMemberAwaitingApproval(ourUuid)))
|
||||||
) {
|
) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`joinViaLink/${logId}: User is part of group on second check, opening conversation`
|
`joinViaLink/${logId}: User is part of group on second check, opening conversation`
|
||||||
|
|
4
ts/model-types.d.ts
vendored
4
ts/model-types.d.ts
vendored
|
@ -21,7 +21,7 @@ import { AttachmentDraftType, AttachmentType } from './types/Attachment';
|
||||||
import { EmbeddedContactType } from './types/EmbeddedContact';
|
import { EmbeddedContactType } from './types/EmbeddedContact';
|
||||||
import { SignalService as Proto } from './protobuf';
|
import { SignalService as Proto } from './protobuf';
|
||||||
import { AvatarDataType } from './types/Avatar';
|
import { AvatarDataType } from './types/Avatar';
|
||||||
import { UUIDStringType } from './types/UUID';
|
import { UUIDStringType, UUIDKind } from './types/UUID';
|
||||||
import { ReactionSource } from './reactions/ReactionSource';
|
import { ReactionSource } from './reactions/ReactionSource';
|
||||||
|
|
||||||
import AccessRequiredEnum = Proto.AccessControl.AccessRequired;
|
import AccessRequiredEnum = Proto.AccessControl.AccessRequired;
|
||||||
|
@ -282,6 +282,8 @@ export type ConversationAttributesType = {
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
profileKeyCredential?: string | null;
|
profileKeyCredential?: string | null;
|
||||||
|
profileKeyCredentialExpiration?: number | null;
|
||||||
|
pniCredential?: string | null;
|
||||||
lastProfile?: ConversationLastProfileType;
|
lastProfile?: ConversationLastProfileType;
|
||||||
quotedMessageId?: string | null;
|
quotedMessageId?: string | null;
|
||||||
sealedSender?: unknown;
|
sealedSender?: unknown;
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
parseNumber,
|
parseNumber,
|
||||||
} from '../util/libphonenumberUtil';
|
} from '../util/libphonenumberUtil';
|
||||||
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
||||||
|
import { toDayMillis } from '../util/timestamp';
|
||||||
import type { AttachmentType } from '../types/Attachment';
|
import type { AttachmentType } from '../types/Attachment';
|
||||||
import { isGIF } from '../types/Attachment';
|
import { isGIF } from '../types/Attachment';
|
||||||
import type { CallHistoryDetailsType } from '../types/Calling';
|
import type { CallHistoryDetailsType } from '../types/Calling';
|
||||||
|
@ -380,7 +381,7 @@ export class ConversationModel extends window.Backbone
|
||||||
return {
|
return {
|
||||||
getGroupId: () => this.get('groupId'),
|
getGroupId: () => this.get('groupId'),
|
||||||
getMembers: () => this.getMembers(),
|
getMembers: () => this.getMembers(),
|
||||||
hasMember: (id: string) => this.hasMember(id),
|
hasMember: (uuid: UUIDStringType) => this.hasMember(new UUID(uuid)),
|
||||||
idForLogging: () => this.idForLogging(),
|
idForLogging: () => this.idForLogging(),
|
||||||
isGroupV2: () => isGroupV2(this.attributes),
|
isGroupV2: () => isGroupV2(this.attributes),
|
||||||
isValid: () => isGroupV2(this.attributes),
|
isValid: () => isGroupV2(this.attributes),
|
||||||
|
@ -393,7 +394,7 @@ export class ConversationModel extends window.Backbone
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
isMemberRequestingToJoin(id: string): boolean {
|
private isMemberRequestingToJoin(uuid: UUID): boolean {
|
||||||
if (!isGroupV2(this.attributes)) {
|
if (!isGroupV2(this.attributes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -403,11 +404,10 @@ export class ConversationModel extends window.Backbone
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uuid = UUID.checkedLookup(id).toString();
|
return pendingAdminApprovalV2.some(item => item.uuid === uuid.toString());
|
||||||
return pendingAdminApprovalV2.some(item => item.uuid === uuid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isMemberPending(id: string): boolean {
|
isMemberPending(uuid: UUID): boolean {
|
||||||
if (!isGroupV2(this.attributes)) {
|
if (!isGroupV2(this.attributes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -417,11 +417,10 @@ export class ConversationModel extends window.Backbone
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uuid = UUID.checkedLookup(id).toString();
|
return pendingMembersV2.some(item => item.uuid === uuid.toString());
|
||||||
return pendingMembersV2.some(item => item.uuid === uuid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isMemberBanned(id: string): boolean {
|
private isMemberBanned(uuid: UUID): boolean {
|
||||||
if (!isGroupV2(this.attributes)) {
|
if (!isGroupV2(this.attributes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -431,11 +430,10 @@ export class ConversationModel extends window.Backbone
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uuid = UUID.checkedLookup(id).toString();
|
return bannedMembersV2.some(member => member.uuid === uuid.toString());
|
||||||
return bannedMembersV2.some(member => member.uuid === uuid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isMemberAwaitingApproval(id: string): boolean {
|
isMemberAwaitingApproval(uuid: UUID): boolean {
|
||||||
if (!isGroupV2(this.attributes)) {
|
if (!isGroupV2(this.attributes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -445,24 +443,22 @@ export class ConversationModel extends window.Backbone
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uuid = UUID.checkedLookup(id).toString();
|
return pendingAdminApprovalV2.some(
|
||||||
return window._.any(pendingAdminApprovalV2, item => item.uuid === uuid);
|
member => member.uuid === uuid.toString()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
isMember(id: string): boolean {
|
isMember(uuid: UUID): boolean {
|
||||||
if (!isGroupV2(this.attributes)) {
|
if (!isGroupV2(this.attributes)) {
|
||||||
throw new Error(
|
return false;
|
||||||
`isMember: Called for non-GroupV2 conversation ${this.idForLogging()}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const membersV2 = this.get('membersV2');
|
const membersV2 = this.get('membersV2');
|
||||||
|
|
||||||
if (!membersV2 || !membersV2.length) {
|
if (!membersV2 || !membersV2.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const uuid = UUID.checkedLookup(id).toString();
|
|
||||||
|
|
||||||
return window._.any(membersV2, item => item.uuid === uuid);
|
return window._.any(membersV2, item => item.uuid === uuid.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateExpirationTimerInGroupV2(
|
async updateExpirationTimerInGroupV2(
|
||||||
|
@ -485,117 +481,101 @@ export class ConversationModel extends window.Backbone
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async promotePendingMember(
|
private async promotePendingMember(
|
||||||
conversationId: string
|
uuidKind: UUIDKind
|
||||||
): Promise<Proto.GroupChange.Actions | undefined> {
|
): Promise<Proto.GroupChange.Actions | undefined> {
|
||||||
const idLog = this.idForLogging();
|
const idLog = this.idForLogging();
|
||||||
|
|
||||||
|
const us = window.ConversationController.getOurConversationOrThrow();
|
||||||
|
const uuid = window.storage.user.getCheckedUuid(uuidKind);
|
||||||
|
|
||||||
// This user's pending state may have changed in the time between the user's
|
// This user's pending state may have changed in the time between the user's
|
||||||
// button press and when we get here. It's especially important to check here
|
// button press and when we get here. It's especially important to check here
|
||||||
// in conflict/retry cases.
|
// in conflict/retry cases.
|
||||||
if (!this.isMemberPending(conversationId)) {
|
if (!this.isMemberPending(uuid)) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`promotePendingMember/${idLog}: ${conversationId} is not a pending member of group. Returning early.`
|
`promotePendingMember/${idLog}: we are not a pending member of group. Returning early.`
|
||||||
);
|
);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pendingMember = window.ConversationController.get(conversationId);
|
|
||||||
if (!pendingMember) {
|
|
||||||
throw new Error(
|
|
||||||
`promotePendingMember/${idLog}: No conversation found for conversation ${conversationId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need the user's profileKeyCredential, which requires a roundtrip with the
|
// We need the user's profileKeyCredential, which requires a roundtrip with the
|
||||||
// server, and most definitely their profileKey. A getProfiles() call will
|
// server, and most definitely their profileKey. A getProfiles() call will
|
||||||
// ensure that we have as much as we can get with the data we have.
|
// ensure that we have as much as we can get with the data we have.
|
||||||
let profileKeyCredentialBase64 = pendingMember.get('profileKeyCredential');
|
if (uuidKind === UUIDKind.ACI) {
|
||||||
if (!profileKeyCredentialBase64) {
|
if (!us.get('profileKeyCredential')) {
|
||||||
await pendingMember.getProfiles();
|
await us.getProfiles();
|
||||||
|
|
||||||
profileKeyCredentialBase64 = pendingMember.get('profileKeyCredential');
|
|
||||||
if (!profileKeyCredentialBase64) {
|
|
||||||
throw new Error(
|
|
||||||
`promotePendingMember/${idLog}: No profileKeyCredential for conversation ${pendingMember.idForLogging()}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const profileKeyCredentialBase64 = us.get('profileKeyCredential');
|
||||||
|
strictAssert(
|
||||||
|
profileKeyCredentialBase64,
|
||||||
|
'Must have profileKeyCredential'
|
||||||
|
);
|
||||||
|
|
||||||
|
return window.Signal.Groups.buildPromoteMemberChange({
|
||||||
|
group: this.attributes,
|
||||||
|
profileKeyCredentialBase64,
|
||||||
|
serverPublicParamsBase64: window.getServerPublicParams(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strictAssert(uuidKind === UUIDKind.PNI, 'Must be a PNI promotion');
|
||||||
|
|
||||||
|
// Similarly we need `pniCredential` even if this would require a server
|
||||||
|
// roundtrip.
|
||||||
|
if (!us.get('pniCredential')) {
|
||||||
|
await us.getProfiles();
|
||||||
|
}
|
||||||
|
const pniCredentialBase64 = us.get('pniCredential');
|
||||||
|
strictAssert(pniCredentialBase64, 'Must have pniCredential');
|
||||||
|
|
||||||
return window.Signal.Groups.buildPromoteMemberChange({
|
return window.Signal.Groups.buildPromoteMemberChange({
|
||||||
group: this.attributes,
|
group: this.attributes,
|
||||||
profileKeyCredentialBase64,
|
pniCredentialBase64,
|
||||||
serverPublicParamsBase64: window.getServerPublicParams(),
|
serverPublicParamsBase64: window.getServerPublicParams(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async approvePendingApprovalRequest(
|
private async approvePendingApprovalRequest(
|
||||||
conversationId: string
|
uuid: UUID
|
||||||
): Promise<Proto.GroupChange.Actions | undefined> {
|
): Promise<Proto.GroupChange.Actions | undefined> {
|
||||||
const idLog = this.idForLogging();
|
const idLog = this.idForLogging();
|
||||||
|
|
||||||
// This user's pending state may have changed in the time between the user's
|
// This user's pending state may have changed in the time between the user's
|
||||||
// button press and when we get here. It's especially important to check here
|
// button press and when we get here. It's especially important to check here
|
||||||
// in conflict/retry cases.
|
// in conflict/retry cases.
|
||||||
if (!this.isMemberRequestingToJoin(conversationId)) {
|
if (!this.isMemberRequestingToJoin(uuid)) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`approvePendingApprovalRequest/${idLog}: ${conversationId} is not requesting to join the group. Returning early.`
|
`approvePendingApprovalRequest/${idLog}: ${uuid} is not requesting ` +
|
||||||
|
'to join the group. Returning early.'
|
||||||
);
|
);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pendingMember = window.ConversationController.get(conversationId);
|
|
||||||
if (!pendingMember) {
|
|
||||||
throw new Error(
|
|
||||||
`approvePendingApprovalRequest/${idLog}: No conversation found for conversation ${conversationId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uuid = pendingMember.get('uuid');
|
|
||||||
if (!uuid) {
|
|
||||||
throw new Error(
|
|
||||||
`approvePendingApprovalRequest/${idLog}: Missing uuid for conversation ${conversationId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return window.Signal.Groups.buildPromotePendingAdminApprovalMemberChange({
|
return window.Signal.Groups.buildPromotePendingAdminApprovalMemberChange({
|
||||||
group: this.attributes,
|
group: this.attributes,
|
||||||
uuid,
|
uuid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async denyPendingApprovalRequest(
|
private async denyPendingApprovalRequest(
|
||||||
conversationId: string
|
uuid: UUID
|
||||||
): Promise<Proto.GroupChange.Actions | undefined> {
|
): Promise<Proto.GroupChange.Actions | undefined> {
|
||||||
const idLog = this.idForLogging();
|
const idLog = this.idForLogging();
|
||||||
|
|
||||||
// This user's pending state may have changed in the time between the user's
|
// This user's pending state may have changed in the time between the user's
|
||||||
// button press and when we get here. It's especially important to check here
|
// button press and when we get here. It's especially important to check here
|
||||||
// in conflict/retry cases.
|
// in conflict/retry cases.
|
||||||
if (!this.isMemberRequestingToJoin(conversationId)) {
|
if (!this.isMemberRequestingToJoin(uuid)) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`denyPendingApprovalRequest/${idLog}: ${conversationId} is not requesting to join the group. Returning early.`
|
`denyPendingApprovalRequest/${idLog}: ${uuid} is not requesting ` +
|
||||||
|
'to join the group. Returning early.'
|
||||||
);
|
);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pendingMember = window.ConversationController.get(conversationId);
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid(UUIDKind.ACI);
|
||||||
if (!pendingMember) {
|
|
||||||
throw new Error(
|
|
||||||
`denyPendingApprovalRequest/${idLog}: No conversation found for conversation ${conversationId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uuid = pendingMember.get('uuid');
|
|
||||||
if (!uuid) {
|
|
||||||
throw new Error(
|
|
||||||
`denyPendingApprovalRequest/${idLog}: Missing uuid for conversation ${pendingMember.idForLogging()}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ourUuid = window.textsecure.storage.user
|
|
||||||
.getCheckedUuid(UUIDKind.ACI)
|
|
||||||
.toString();
|
|
||||||
|
|
||||||
return window.Signal.Groups.buildDeletePendingAdminApprovalMemberChange({
|
return window.Signal.Groups.buildDeletePendingAdminApprovalMemberChange({
|
||||||
group: this.attributes,
|
group: this.attributes,
|
||||||
|
@ -620,6 +600,8 @@ export class ConversationModel extends window.Backbone
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uuid = toRequest.getCheckedUuid(`addPendingApprovalRequest/${idLog}`);
|
||||||
|
|
||||||
// We need the user's profileKeyCredential, which requires a roundtrip with the
|
// We need the user's profileKeyCredential, which requires a roundtrip with the
|
||||||
// server, and most definitely their profileKey. A getProfiles() call will
|
// server, and most definitely their profileKey. A getProfiles() call will
|
||||||
// ensure that we have as much as we can get with the data we have.
|
// ensure that we have as much as we can get with the data we have.
|
||||||
|
@ -638,9 +620,10 @@ export class ConversationModel extends window.Backbone
|
||||||
// This user's pending state may have changed in the time between the user's
|
// This user's pending state may have changed in the time between the user's
|
||||||
// button press and when we get here. It's especially important to check here
|
// button press and when we get here. It's especially important to check here
|
||||||
// in conflict/retry cases.
|
// in conflict/retry cases.
|
||||||
if (this.isMemberAwaitingApproval(conversationId)) {
|
if (this.isMemberAwaitingApproval(uuid)) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`addPendingApprovalRequest/${idLog}: ${conversationId} already in pending approval.`
|
`addPendingApprovalRequest/${idLog}: ` +
|
||||||
|
`${toRequest.idForLogging()} already in pending approval.`
|
||||||
);
|
);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -652,23 +635,12 @@ export class ConversationModel extends window.Backbone
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async addMember(
|
async addMember(uuid: UUID): Promise<Proto.GroupChange.Actions | undefined> {
|
||||||
conversationId: string
|
|
||||||
): Promise<Proto.GroupChange.Actions | undefined> {
|
|
||||||
const idLog = this.idForLogging();
|
const idLog = this.idForLogging();
|
||||||
|
|
||||||
const toRequest = window.ConversationController.get(conversationId);
|
const toRequest = window.ConversationController.get(uuid.toString());
|
||||||
if (!toRequest) {
|
if (!toRequest) {
|
||||||
throw new Error(
|
throw new Error(`addMember/${idLog}: No conversation found for ${uuid}`);
|
||||||
`addMember/${idLog}: No conversation found for conversation ${conversationId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uuid = toRequest.get('uuid');
|
|
||||||
if (!uuid) {
|
|
||||||
throw new Error(
|
|
||||||
`addMember/${idLog}: ${toRequest.idForLogging()} is missing a uuid!`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need the user's profileKeyCredential, which requires a roundtrip with the
|
// We need the user's profileKeyCredential, which requires a roundtrip with the
|
||||||
|
@ -689,8 +661,11 @@ export class ConversationModel extends window.Backbone
|
||||||
// This user's pending state may have changed in the time between the user's
|
// This user's pending state may have changed in the time between the user's
|
||||||
// button press and when we get here. It's especially important to check here
|
// button press and when we get here. It's especially important to check here
|
||||||
// in conflict/retry cases.
|
// in conflict/retry cases.
|
||||||
if (this.isMember(conversationId)) {
|
if (this.isMember(uuid)) {
|
||||||
log.warn(`addMember/${idLog}: ${conversationId} already a member.`);
|
log.warn(
|
||||||
|
`addMember/${idLog}: ${toRequest.idForLogging()} ` +
|
||||||
|
'is already a member.'
|
||||||
|
);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -702,38 +677,23 @@ export class ConversationModel extends window.Backbone
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async removePendingMember(
|
private async removePendingMember(
|
||||||
conversationIds: Array<string>
|
uuids: ReadonlyArray<UUID>
|
||||||
): Promise<Proto.GroupChange.Actions | undefined> {
|
): Promise<Proto.GroupChange.Actions | undefined> {
|
||||||
const idLog = this.idForLogging();
|
const idLog = this.idForLogging();
|
||||||
|
|
||||||
const uuids = conversationIds
|
const pendingUuids = uuids
|
||||||
.map(conversationId => {
|
.map(uuid => {
|
||||||
// This user's pending state may have changed in the time between the user's
|
// This user's pending state may have changed in the time between the user's
|
||||||
// button press and when we get here. It's especially important to check here
|
// button press and when we get here. It's especially important to check here
|
||||||
// in conflict/retry cases.
|
// in conflict/retry cases.
|
||||||
if (!this.isMemberPending(conversationId)) {
|
if (!this.isMemberPending(uuid)) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`removePendingMember/${idLog}: ${conversationId} is not a pending member of group. Returning early.`
|
`removePendingMember/${idLog}: ${uuid} is not a pending member of group. Returning early.`
|
||||||
);
|
);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pendingMember = window.ConversationController.get(conversationId);
|
|
||||||
if (!pendingMember) {
|
|
||||||
log.warn(
|
|
||||||
`removePendingMember/${idLog}: No conversation found for conversation ${conversationId}`
|
|
||||||
);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uuid = pendingMember.get('uuid');
|
|
||||||
if (!uuid) {
|
|
||||||
log.warn(
|
|
||||||
`removePendingMember/${idLog}: Missing uuid for conversation ${pendingMember.idForLogging()}`
|
|
||||||
);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return uuid;
|
return uuid;
|
||||||
})
|
})
|
||||||
.filter(isNotNil);
|
.filter(isNotNil);
|
||||||
|
@ -744,42 +704,26 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
return window.Signal.Groups.buildDeletePendingMemberChange({
|
return window.Signal.Groups.buildDeletePendingMemberChange({
|
||||||
group: this.attributes,
|
group: this.attributes,
|
||||||
uuids,
|
uuids: pendingUuids,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeMember(
|
private async removeMember(
|
||||||
conversationId: string
|
uuid: UUID
|
||||||
): Promise<Proto.GroupChange.Actions | undefined> {
|
): Promise<Proto.GroupChange.Actions | undefined> {
|
||||||
const idLog = this.idForLogging();
|
const idLog = this.idForLogging();
|
||||||
|
|
||||||
// This user's pending state may have changed in the time between the user's
|
// This user's pending state may have changed in the time between the user's
|
||||||
// button press and when we get here. It's especially important to check here
|
// button press and when we get here. It's especially important to check here
|
||||||
// in conflict/retry cases.
|
// in conflict/retry cases.
|
||||||
if (!this.isMember(conversationId)) {
|
if (!this.isMember(uuid)) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`removeMember/${idLog}: ${conversationId} is not a pending member of group. Returning early.`
|
`removeMember/${idLog}: ${uuid} is not a pending member of group. Returning early.`
|
||||||
);
|
);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const member = window.ConversationController.get(conversationId);
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid(UUIDKind.ACI);
|
||||||
if (!member) {
|
|
||||||
throw new Error(
|
|
||||||
`removeMember/${idLog}: No conversation found for conversation ${conversationId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uuid = member.get('uuid');
|
|
||||||
if (!uuid) {
|
|
||||||
throw new Error(
|
|
||||||
`removeMember/${idLog}: Missing uuid for conversation ${member.idForLogging()}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ourUuid = window.textsecure.storage.user
|
|
||||||
.getCheckedUuid(UUIDKind.ACI)
|
|
||||||
.toString();
|
|
||||||
|
|
||||||
return window.Signal.Groups.buildDeleteMemberChange({
|
return window.Signal.Groups.buildDeleteMemberChange({
|
||||||
group: this.attributes,
|
group: this.attributes,
|
||||||
|
@ -788,8 +732,8 @@ export class ConversationModel extends window.Backbone
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async toggleAdminChange(
|
private async toggleAdminChange(
|
||||||
conversationId: string
|
uuid: UUID
|
||||||
): Promise<Proto.GroupChange.Actions | undefined> {
|
): Promise<Proto.GroupChange.Actions | undefined> {
|
||||||
if (!isGroupV2(this.attributes)) {
|
if (!isGroupV2(this.attributes)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -797,30 +741,16 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
const idLog = this.idForLogging();
|
const idLog = this.idForLogging();
|
||||||
|
|
||||||
if (!this.isMember(conversationId)) {
|
if (!this.isMember(uuid)) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`toggleAdminChange/${idLog}: ${conversationId} is not a pending member of group. Returning early.`
|
`toggleAdminChange/${idLog}: ${uuid} is not a pending member of group. Returning early.`
|
||||||
);
|
);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
|
||||||
if (!conversation) {
|
|
||||||
throw new Error(
|
|
||||||
`toggleAdminChange/${idLog}: No conversation found for conversation ${conversationId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uuid = conversation.get('uuid');
|
|
||||||
if (!uuid) {
|
|
||||||
throw new Error(
|
|
||||||
`toggleAdminChange/${idLog}: Missing uuid for conversation ${conversationId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const MEMBER_ROLES = Proto.Member.Role;
|
const MEMBER_ROLES = Proto.Member.Role;
|
||||||
|
|
||||||
const role = this.isAdmin(conversationId)
|
const role = this.isAdmin(uuid)
|
||||||
? MEMBER_ROLES.DEFAULT
|
? MEMBER_ROLES.DEFAULT
|
||||||
: MEMBER_ROLES.ADMINISTRATOR;
|
: MEMBER_ROLES.ADMINISTRATOR;
|
||||||
|
|
||||||
|
@ -832,11 +762,13 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
|
|
||||||
async modifyGroupV2({
|
async modifyGroupV2({
|
||||||
|
usingCredentialsFrom,
|
||||||
createGroupChange,
|
createGroupChange,
|
||||||
extraConversationsForSend,
|
extraConversationsForSend,
|
||||||
inviteLinkPassword,
|
inviteLinkPassword,
|
||||||
name,
|
name,
|
||||||
}: {
|
}: {
|
||||||
|
usingCredentialsFrom: ReadonlyArray<ConversationModel>;
|
||||||
createGroupChange: () => Promise<Proto.GroupChange.Actions | undefined>;
|
createGroupChange: () => Promise<Proto.GroupChange.Actions | undefined>;
|
||||||
extraConversationsForSend?: Array<string>;
|
extraConversationsForSend?: Array<string>;
|
||||||
inviteLinkPassword?: string;
|
inviteLinkPassword?: string;
|
||||||
|
@ -844,6 +776,7 @@ export class ConversationModel extends window.Backbone
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
await window.Signal.Groups.modifyGroupV2({
|
await window.Signal.Groups.modifyGroupV2({
|
||||||
conversation: this,
|
conversation: this,
|
||||||
|
usingCredentialsFrom,
|
||||||
createGroupChange,
|
createGroupChange,
|
||||||
extraConversationsForSend,
|
extraConversationsForSend,
|
||||||
inviteLinkPassword,
|
inviteLinkPassword,
|
||||||
|
@ -1828,6 +1761,9 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
const { customColor, customColorId } = this.getCustomColorData();
|
const { customColor, customColorId } = this.getCustomColorData();
|
||||||
|
|
||||||
|
const ourACI = window.textsecure.storage.user.getCheckedUuid(UUIDKind.ACI);
|
||||||
|
const ourPNI = window.textsecure.storage.user.getUuid(UUIDKind.PNI);
|
||||||
|
|
||||||
// TODO: DESKTOP-720
|
// TODO: DESKTOP-720
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
@ -1844,11 +1780,13 @@ export class ConversationModel extends window.Backbone
|
||||||
acceptedMessageRequest: this.getAccepted(),
|
acceptedMessageRequest: this.getAccepted(),
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
activeAt: this.get('active_at')!,
|
activeAt: this.get('active_at')!,
|
||||||
areWePending: Boolean(
|
areWePending:
|
||||||
ourConversationId && this.isMemberPending(ourConversationId)
|
this.isMemberPending(ourACI) ||
|
||||||
),
|
Boolean(
|
||||||
|
ourPNI && !this.isMember(ourACI) && this.isMemberPending(ourPNI)
|
||||||
|
),
|
||||||
areWePendingApproval: Boolean(
|
areWePendingApproval: Boolean(
|
||||||
ourConversationId && this.isMemberAwaitingApproval(ourConversationId)
|
ourConversationId && this.isMemberAwaitingApproval(ourACI)
|
||||||
),
|
),
|
||||||
areWeAdmin: this.areWeAdmin(),
|
areWeAdmin: this.areWeAdmin(),
|
||||||
avatars: getAvatarData(this.attributes),
|
avatars: getAvatarData(this.attributes),
|
||||||
|
@ -2093,8 +2031,6 @@ export class ConversationModel extends window.Backbone
|
||||||
try {
|
try {
|
||||||
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
||||||
const isLocalAction = !fromSync && !viaStorageServiceSync;
|
const isLocalAction = !fromSync && !viaStorageServiceSync;
|
||||||
const ourConversationId =
|
|
||||||
window.ConversationController.getOurConversationId();
|
|
||||||
|
|
||||||
const currentMessageRequestState = this.get('messageRequestResponseType');
|
const currentMessageRequestState = this.get('messageRequestResponseType');
|
||||||
const didResponseChange = response !== currentMessageRequestState;
|
const didResponseChange = response !== currentMessageRequestState;
|
||||||
|
@ -2116,26 +2052,36 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLocalAction) {
|
if (isLocalAction) {
|
||||||
|
const ourACI = window.textsecure.storage.user.getCheckedUuid(
|
||||||
|
UUIDKind.ACI
|
||||||
|
);
|
||||||
|
const ourPNI = window.textsecure.storage.user.getUuid(UUIDKind.PNI);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isGroupV1(this.attributes) ||
|
isGroupV1(this.attributes) ||
|
||||||
isDirectConversation(this.attributes)
|
isDirectConversation(this.attributes)
|
||||||
) {
|
) {
|
||||||
this.sendProfileKeyUpdate();
|
this.sendProfileKeyUpdate();
|
||||||
} else if (
|
} else if (
|
||||||
ourConversationId &&
|
|
||||||
isGroupV2(this.attributes) &&
|
isGroupV2(this.attributes) &&
|
||||||
this.isMemberPending(ourConversationId)
|
this.isMemberPending(ourACI)
|
||||||
) {
|
) {
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'promotePendingMember',
|
name: 'promotePendingMember',
|
||||||
createGroupChange: () =>
|
usingCredentialsFrom: [],
|
||||||
this.promotePendingMember(ourConversationId),
|
createGroupChange: () => this.promotePendingMember(UUIDKind.ACI),
|
||||||
});
|
});
|
||||||
} else if (
|
} else if (
|
||||||
ourConversationId &&
|
ourPNI &&
|
||||||
isGroupV2(this.attributes) &&
|
isGroupV2(this.attributes) &&
|
||||||
this.isMember(ourConversationId)
|
this.isMemberPending(ourPNI)
|
||||||
) {
|
) {
|
||||||
|
await this.modifyGroupV2({
|
||||||
|
name: 'promotePendingMember',
|
||||||
|
usingCredentialsFrom: [],
|
||||||
|
createGroupChange: () => this.promotePendingMember(UUIDKind.PNI),
|
||||||
|
});
|
||||||
|
} else if (isGroupV2(this.attributes) && this.isMember(ourACI)) {
|
||||||
log.info(
|
log.info(
|
||||||
'applyMessageRequestResponse/accept: Already a member of v2 group'
|
'applyMessageRequestResponse/accept: Already a member of v2 group'
|
||||||
);
|
);
|
||||||
|
@ -2223,21 +2169,21 @@ export class ConversationModel extends window.Backbone
|
||||||
inviteLinkPassword: string;
|
inviteLinkPassword: string;
|
||||||
approvalRequired: boolean;
|
approvalRequired: boolean;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const ourConversationId =
|
const ourACI = window.textsecure.storage.user.getCheckedUuid();
|
||||||
window.ConversationController.getOurConversationIdOrThrow();
|
|
||||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid().toString();
|
|
||||||
try {
|
try {
|
||||||
if (approvalRequired) {
|
if (approvalRequired) {
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'requestToJoin',
|
name: 'requestToJoin',
|
||||||
|
usingCredentialsFrom: [],
|
||||||
inviteLinkPassword,
|
inviteLinkPassword,
|
||||||
createGroupChange: () => this.addPendingApprovalRequest(),
|
createGroupChange: () => this.addPendingApprovalRequest(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'joinGroup',
|
name: 'joinGroup',
|
||||||
|
usingCredentialsFrom: [],
|
||||||
inviteLinkPassword,
|
inviteLinkPassword,
|
||||||
createGroupChange: () => this.addMember(ourConversationId),
|
createGroupChange: () => this.addMember(ourACI),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -2256,7 +2202,7 @@ export class ConversationModel extends window.Backbone
|
||||||
this.set({
|
this.set({
|
||||||
pendingAdminApprovalV2: [
|
pendingAdminApprovalV2: [
|
||||||
{
|
{
|
||||||
uuid: ourUuid,
|
uuid: ourACI.toString(),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -2277,8 +2223,7 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancelJoinRequest(): Promise<void> {
|
async cancelJoinRequest(): Promise<void> {
|
||||||
const ourConversationId =
|
const ourACI = window.storage.user.getCheckedUuid(UUIDKind.ACI);
|
||||||
window.ConversationController.getOurConversationIdOrThrow();
|
|
||||||
|
|
||||||
const inviteLinkPassword = this.get('groupInviteLinkPassword');
|
const inviteLinkPassword = this.get('groupInviteLinkPassword');
|
||||||
if (!inviteLinkPassword) {
|
if (!inviteLinkPassword) {
|
||||||
|
@ -2289,15 +2234,18 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'cancelJoinRequest',
|
name: 'cancelJoinRequest',
|
||||||
|
usingCredentialsFrom: [],
|
||||||
inviteLinkPassword,
|
inviteLinkPassword,
|
||||||
createGroupChange: () =>
|
createGroupChange: () => this.denyPendingApprovalRequest(ourACI),
|
||||||
this.denyPendingApprovalRequest(ourConversationId),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async addMembersV2(conversationIds: ReadonlyArray<string>): Promise<void> {
|
async addMembersV2(conversationIds: ReadonlyArray<string>): Promise<void> {
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'addMembersV2',
|
name: 'addMembersV2',
|
||||||
|
usingCredentialsFrom: conversationIds
|
||||||
|
.map(id => window.ConversationController.get(id))
|
||||||
|
.filter(isNotNil),
|
||||||
createGroupChange: () =>
|
createGroupChange: () =>
|
||||||
window.Signal.Groups.buildAddMembersChange(
|
window.Signal.Groups.buildAddMembersChange(
|
||||||
this.attributes,
|
this.attributes,
|
||||||
|
@ -2315,6 +2263,7 @@ export class ConversationModel extends window.Backbone
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'updateGroupAttributesV2',
|
name: 'updateGroupAttributesV2',
|
||||||
|
usingCredentialsFrom: [],
|
||||||
createGroupChange: () =>
|
createGroupChange: () =>
|
||||||
window.Signal.Groups.buildUpdateAttributesChange(
|
window.Signal.Groups.buildUpdateAttributesChange(
|
||||||
{
|
{
|
||||||
|
@ -2329,36 +2278,43 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
|
|
||||||
async leaveGroupV2(): Promise<void> {
|
async leaveGroupV2(): Promise<void> {
|
||||||
const ourConversationId =
|
if (!isGroupV2(this.attributes)) {
|
||||||
window.ConversationController.getOurConversationId();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
const ourACI = window.textsecure.storage.user.getCheckedUuid(UUIDKind.ACI);
|
||||||
ourConversationId &&
|
const ourPNI = window.textsecure.storage.user.getUuid(UUIDKind.PNI);
|
||||||
isGroupV2(this.attributes) &&
|
|
||||||
this.isMemberPending(ourConversationId)
|
if (this.isMemberPending(ourACI)) {
|
||||||
) {
|
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'delete',
|
name: 'delete',
|
||||||
createGroupChange: () => this.removePendingMember([ourConversationId]),
|
usingCredentialsFrom: [],
|
||||||
|
createGroupChange: () => this.removePendingMember([ourACI]),
|
||||||
});
|
});
|
||||||
} else if (
|
} else if (this.isMember(ourACI)) {
|
||||||
ourConversationId &&
|
|
||||||
isGroupV2(this.attributes) &&
|
|
||||||
this.isMember(ourConversationId)
|
|
||||||
) {
|
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'delete',
|
name: 'delete',
|
||||||
createGroupChange: () => this.removeMember(ourConversationId),
|
usingCredentialsFrom: [],
|
||||||
|
createGroupChange: () => this.removeMember(ourACI),
|
||||||
|
});
|
||||||
|
// Keep PNI in pending if ACI was a member.
|
||||||
|
} else if (ourPNI && this.isMemberPending(ourPNI)) {
|
||||||
|
await this.modifyGroupV2({
|
||||||
|
name: 'delete',
|
||||||
|
usingCredentialsFrom: [],
|
||||||
|
createGroupChange: () => this.removePendingMember([ourPNI]),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
const logId = this.idForLogging();
|
||||||
log.error(
|
log.error(
|
||||||
'leaveGroupV2: We were neither a member nor a pending member of the group'
|
'leaveGroupV2: We were neither a member nor a pending member of ' +
|
||||||
|
`the group ${logId}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async addBannedMember(
|
async addBannedMember(
|
||||||
uuid: UUIDStringType
|
uuid: UUID
|
||||||
): Promise<Proto.GroupChange.Actions | undefined> {
|
): Promise<Proto.GroupChange.Actions | undefined> {
|
||||||
if (this.isMember(uuid)) {
|
if (this.isMember(uuid)) {
|
||||||
log.warn('addBannedMember: Member is a part of the group!');
|
log.warn('addBannedMember: Member is a part of the group!');
|
||||||
|
@ -2387,7 +2343,8 @@ export class ConversationModel extends window.Backbone
|
||||||
async blockGroupLinkRequests(uuid: UUIDStringType): Promise<void> {
|
async blockGroupLinkRequests(uuid: UUIDStringType): Promise<void> {
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'addBannedMember',
|
name: 'addBannedMember',
|
||||||
createGroupChange: async () => this.addBannedMember(uuid),
|
usingCredentialsFrom: [],
|
||||||
|
createGroupChange: async () => this.addBannedMember(new UUID(uuid)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2396,7 +2353,17 @@ export class ConversationModel extends window.Backbone
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isMember(conversationId)) {
|
const logId = this.idForLogging();
|
||||||
|
|
||||||
|
const member = window.ConversationController.get(conversationId);
|
||||||
|
if (!member) {
|
||||||
|
log.error(`toggleAdmin/${logId}: ${conversationId} does not exist`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uuid = member.getCheckedUuid(`toggleAdmin/${logId}`);
|
||||||
|
|
||||||
|
if (!this.isMember(uuid)) {
|
||||||
log.error(
|
log.error(
|
||||||
`toggleAdmin: Member ${conversationId} is not a member of the group`
|
`toggleAdmin: Member ${conversationId} is not a member of the group`
|
||||||
);
|
);
|
||||||
|
@ -2405,21 +2372,32 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'toggleAdmin',
|
name: 'toggleAdmin',
|
||||||
createGroupChange: () => this.toggleAdminChange(conversationId),
|
usingCredentialsFrom: [member],
|
||||||
|
createGroupChange: () => this.toggleAdminChange(uuid),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async approvePendingMembershipFromGroupV2(
|
async approvePendingMembershipFromGroupV2(
|
||||||
conversationId: string
|
conversationId: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (
|
const logId = this.idForLogging();
|
||||||
isGroupV2(this.attributes) &&
|
|
||||||
this.isMemberRequestingToJoin(conversationId)
|
const pendingMember = window.ConversationController.get(conversationId);
|
||||||
) {
|
if (!pendingMember) {
|
||||||
|
throw new Error(
|
||||||
|
`approvePendingMembershipFromGroupV2/${logId}: No conversation found for conversation ${conversationId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uuid = pendingMember.getCheckedUuid(
|
||||||
|
`approvePendingMembershipFromGroupV2/${logId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isGroupV2(this.attributes) && this.isMemberRequestingToJoin(uuid)) {
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'approvePendingApprovalRequest',
|
name: 'approvePendingApprovalRequest',
|
||||||
createGroupChange: () =>
|
usingCredentialsFrom: [pendingMember],
|
||||||
this.approvePendingApprovalRequest(conversationId),
|
createGroupChange: () => this.approvePendingApprovalRequest(uuid),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2431,55 +2409,89 @@ export class ConversationModel extends window.Backbone
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [conversationId] = conversationIds;
|
|
||||||
|
|
||||||
// Only pending memberships can be revoked for multiple members at once
|
// Only pending memberships can be revoked for multiple members at once
|
||||||
if (conversationIds.length > 1) {
|
if (conversationIds.length > 1) {
|
||||||
|
const uuids = conversationIds.map(id => {
|
||||||
|
const uuid = window.ConversationController.get(id)?.getUuid();
|
||||||
|
strictAssert(uuid, `UUID does not exist for ${id}`);
|
||||||
|
return uuid;
|
||||||
|
});
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'removePendingMember',
|
name: 'removePendingMember',
|
||||||
createGroupChange: () => this.removePendingMember(conversationIds),
|
usingCredentialsFrom: conversationIds
|
||||||
|
.map(id => window.ConversationController.get(id))
|
||||||
|
.filter(isNotNil),
|
||||||
|
createGroupChange: () => this.removePendingMember(uuids),
|
||||||
extraConversationsForSend: conversationIds,
|
extraConversationsForSend: conversationIds,
|
||||||
});
|
});
|
||||||
} else if (this.isMemberRequestingToJoin(conversationId)) {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [conversationId] = conversationIds;
|
||||||
|
|
||||||
|
const pendingMember = window.ConversationController.get(conversationId);
|
||||||
|
if (!pendingMember) {
|
||||||
|
const logId = this.idForLogging();
|
||||||
|
throw new Error(
|
||||||
|
`revokePendingMembershipsFromGroupV2/${logId}: No conversation found for conversation ${conversationId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uuid = pendingMember.getCheckedUuid(
|
||||||
|
'revokePendingMembershipsFromGroupV2'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.isMemberRequestingToJoin(uuid)) {
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'denyPendingApprovalRequest',
|
name: 'denyPendingApprovalRequest',
|
||||||
createGroupChange: () =>
|
usingCredentialsFrom: [pendingMember],
|
||||||
this.denyPendingApprovalRequest(conversationId),
|
createGroupChange: () => this.denyPendingApprovalRequest(uuid),
|
||||||
extraConversationsForSend: [conversationId],
|
extraConversationsForSend: [conversationId],
|
||||||
});
|
});
|
||||||
} else if (this.isMemberPending(conversationId)) {
|
} else if (this.isMemberPending(uuid)) {
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'removePendingMember',
|
name: 'removePendingMember',
|
||||||
createGroupChange: () => this.removePendingMember([conversationId]),
|
usingCredentialsFrom: [pendingMember],
|
||||||
|
createGroupChange: () => this.removePendingMember([uuid]),
|
||||||
extraConversationsForSend: [conversationId],
|
extraConversationsForSend: [conversationId],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeFromGroupV2(conversationId: string): Promise<void> {
|
async removeFromGroupV2(conversationId: string): Promise<void> {
|
||||||
if (
|
if (!isGroupV2(this.attributes)) {
|
||||||
isGroupV2(this.attributes) &&
|
return;
|
||||||
this.isMemberRequestingToJoin(conversationId)
|
}
|
||||||
) {
|
|
||||||
|
const logId = this.idForLogging();
|
||||||
|
const pendingMember = window.ConversationController.get(conversationId);
|
||||||
|
if (!pendingMember) {
|
||||||
|
throw new Error(
|
||||||
|
`removeFromGroupV2/${logId}: No conversation found for conversation ${conversationId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uuid = pendingMember.getCheckedUuid(`removeFromGroupV2/${logId}`);
|
||||||
|
|
||||||
|
if (this.isMemberRequestingToJoin(uuid)) {
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'denyPendingApprovalRequest',
|
name: 'denyPendingApprovalRequest',
|
||||||
createGroupChange: () =>
|
usingCredentialsFrom: [pendingMember],
|
||||||
this.denyPendingApprovalRequest(conversationId),
|
createGroupChange: () => this.denyPendingApprovalRequest(uuid),
|
||||||
extraConversationsForSend: [conversationId],
|
extraConversationsForSend: [conversationId],
|
||||||
});
|
});
|
||||||
} else if (
|
} else if (this.isMemberPending(uuid)) {
|
||||||
isGroupV2(this.attributes) &&
|
|
||||||
this.isMemberPending(conversationId)
|
|
||||||
) {
|
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'removePendingMember',
|
name: 'removePendingMember',
|
||||||
createGroupChange: () => this.removePendingMember([conversationId]),
|
usingCredentialsFrom: [pendingMember],
|
||||||
|
createGroupChange: () => this.removePendingMember([uuid]),
|
||||||
extraConversationsForSend: [conversationId],
|
extraConversationsForSend: [conversationId],
|
||||||
});
|
});
|
||||||
} else if (isGroupV2(this.attributes) && this.isMember(conversationId)) {
|
} else if (this.isMember(uuid)) {
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'removeFromGroup',
|
name: 'removeFromGroup',
|
||||||
createGroupChange: () => this.removeMember(conversationId),
|
usingCredentialsFrom: [pendingMember],
|
||||||
|
createGroupChange: () => this.removeMember(uuid),
|
||||||
extraConversationsForSend: [conversationId],
|
extraConversationsForSend: [conversationId],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -3478,14 +3490,13 @@ export class ConversationModel extends window.Backbone
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isAdmin(id: string): boolean {
|
isAdmin(uuid: UUID): boolean {
|
||||||
if (!isGroupV2(this.attributes)) {
|
if (!isGroupV2(this.attributes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uuid = UUID.checkedLookup(id).toString();
|
|
||||||
const members = this.get('membersV2') || [];
|
const members = this.get('membersV2') || [];
|
||||||
const member = members.find(x => x.uuid === uuid);
|
const member = members.find(x => x.uuid === uuid.toString());
|
||||||
if (!member) {
|
if (!member) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -4187,6 +4198,7 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'updateInviteLinkPassword',
|
name: 'updateInviteLinkPassword',
|
||||||
|
usingCredentialsFrom: [],
|
||||||
createGroupChange: async () =>
|
createGroupChange: async () =>
|
||||||
window.Signal.Groups.buildInviteLinkPasswordChange(
|
window.Signal.Groups.buildInviteLinkPasswordChange(
|
||||||
this.attributes,
|
this.attributes,
|
||||||
|
@ -4218,6 +4230,7 @@ export class ConversationModel extends window.Backbone
|
||||||
if (shouldCreateNewGroupLink) {
|
if (shouldCreateNewGroupLink) {
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'updateNewGroupLink',
|
name: 'updateNewGroupLink',
|
||||||
|
usingCredentialsFrom: [],
|
||||||
createGroupChange: async () =>
|
createGroupChange: async () =>
|
||||||
window.Signal.Groups.buildNewGroupLinkChange(
|
window.Signal.Groups.buildNewGroupLinkChange(
|
||||||
this.attributes,
|
this.attributes,
|
||||||
|
@ -4228,6 +4241,7 @@ export class ConversationModel extends window.Backbone
|
||||||
} else {
|
} else {
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'updateAccessControlAddFromInviteLink',
|
name: 'updateAccessControlAddFromInviteLink',
|
||||||
|
usingCredentialsFrom: [],
|
||||||
createGroupChange: async () =>
|
createGroupChange: async () =>
|
||||||
window.Signal.Groups.buildAccessControlAddFromInviteLinkChange(
|
window.Signal.Groups.buildAccessControlAddFromInviteLinkChange(
|
||||||
this.attributes,
|
this.attributes,
|
||||||
|
@ -4262,6 +4276,7 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'updateAccessControlAddFromInviteLink',
|
name: 'updateAccessControlAddFromInviteLink',
|
||||||
|
usingCredentialsFrom: [],
|
||||||
createGroupChange: async () =>
|
createGroupChange: async () =>
|
||||||
window.Signal.Groups.buildAccessControlAddFromInviteLinkChange(
|
window.Signal.Groups.buildAccessControlAddFromInviteLinkChange(
|
||||||
this.attributes,
|
this.attributes,
|
||||||
|
@ -4285,6 +4300,7 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'updateAccessControlAttributes',
|
name: 'updateAccessControlAttributes',
|
||||||
|
usingCredentialsFrom: [],
|
||||||
createGroupChange: async () =>
|
createGroupChange: async () =>
|
||||||
window.Signal.Groups.buildAccessControlAttributesChange(
|
window.Signal.Groups.buildAccessControlAttributesChange(
|
||||||
this.attributes,
|
this.attributes,
|
||||||
|
@ -4310,6 +4326,7 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'updateAccessControlMembers',
|
name: 'updateAccessControlMembers',
|
||||||
|
usingCredentialsFrom: [],
|
||||||
createGroupChange: async () =>
|
createGroupChange: async () =>
|
||||||
window.Signal.Groups.buildAccessControlMembersChange(
|
window.Signal.Groups.buildAccessControlMembersChange(
|
||||||
this.attributes,
|
this.attributes,
|
||||||
|
@ -4335,6 +4352,7 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'updateAnnouncementsOnly',
|
name: 'updateAnnouncementsOnly',
|
||||||
|
usingCredentialsFrom: [],
|
||||||
createGroupChange: async () =>
|
createGroupChange: async () =>
|
||||||
window.Signal.Groups.buildAnnouncementsOnlyChange(
|
window.Signal.Groups.buildAnnouncementsOnlyChange(
|
||||||
this.attributes,
|
this.attributes,
|
||||||
|
@ -4377,6 +4395,7 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
await this.modifyGroupV2({
|
await this.modifyGroupV2({
|
||||||
name: 'updateExpirationTimer',
|
name: 'updateExpirationTimer',
|
||||||
|
usingCredentialsFrom: [],
|
||||||
createGroupChange: () =>
|
createGroupChange: () =>
|
||||||
this.updateExpirationTimerInGroupV2(providedExpireTimer),
|
this.updateExpirationTimerInGroupV2(providedExpireTimer),
|
||||||
});
|
});
|
||||||
|
@ -4588,10 +4607,7 @@ export class ConversationModel extends window.Backbone
|
||||||
const ourGroups =
|
const ourGroups =
|
||||||
await window.ConversationController.getAllGroupsInvolvingUuid(ourUuid);
|
await window.ConversationController.getAllGroupsInvolvingUuid(ourUuid);
|
||||||
const sharedGroups = ourGroups
|
const sharedGroups = ourGroups
|
||||||
.filter(
|
.filter(c => c.hasMember(ourUuid) && c.hasMember(theirUuid))
|
||||||
c =>
|
|
||||||
c.hasMember(ourUuid.toString()) && c.hasMember(theirUuid.toString())
|
|
||||||
)
|
|
||||||
.sort(
|
.sort(
|
||||||
(left, right) =>
|
(left, right) =>
|
||||||
(right.get('timestamp') || 0) - (left.get('timestamp') || 0)
|
(right.get('timestamp') || 0) - (left.get('timestamp') || 0)
|
||||||
|
@ -4733,6 +4749,8 @@ export class ConversationModel extends window.Backbone
|
||||||
);
|
);
|
||||||
this.set({
|
this.set({
|
||||||
profileKeyCredential: null,
|
profileKeyCredential: null,
|
||||||
|
profileKeyCredentialExpiration: null,
|
||||||
|
pniCredential: null,
|
||||||
accessKey: null,
|
accessKey: null,
|
||||||
sealedSender: SEALED_SENDER.UNKNOWN,
|
sealedSender: SEALED_SENDER.UNKNOWN,
|
||||||
});
|
});
|
||||||
|
@ -4759,6 +4777,27 @@ export class ConversationModel extends window.Backbone
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasProfileKeyCredentialExpired(): boolean {
|
||||||
|
const profileKeyCredential = this.get('profileKeyCredential');
|
||||||
|
const profileKeyCredentialExpiration = this.get(
|
||||||
|
'profileKeyCredentialExpiration'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!profileKeyCredential) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNumber(profileKeyCredentialExpiration)) {
|
||||||
|
const logId = this.idForLogging();
|
||||||
|
log.warn(`hasProfileKeyCredentialExpired(${logId}): missing expiration`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const today = toDayMillis(Date.now());
|
||||||
|
|
||||||
|
return profileKeyCredentialExpiration <= today;
|
||||||
|
}
|
||||||
|
|
||||||
deriveAccessKeyIfNeeded(): void {
|
deriveAccessKeyIfNeeded(): void {
|
||||||
const profileKey = this.get('profileKey');
|
const profileKey = this.get('profileKey');
|
||||||
if (!profileKey) {
|
if (!profileKey) {
|
||||||
|
@ -4860,11 +4899,10 @@ export class ConversationModel extends window.Backbone
|
||||||
await window.Signal.Data.updateConversation(this.attributes);
|
await window.Signal.Data.updateConversation(this.attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMember(identifier: string): boolean {
|
hasMember(uuid: UUID): boolean {
|
||||||
const id = window.ConversationController.getConversationId(identifier);
|
const members = this.getMembers();
|
||||||
const memberIds = this.getMemberIds();
|
|
||||||
|
|
||||||
return window._.contains(memberIds, id);
|
return members.some(member => member.get('uuid') === uuid.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchContacts(): void {
|
fetchContacts(): void {
|
||||||
|
@ -5234,9 +5272,19 @@ export class ConversationModel extends window.Backbone
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sender = window.ConversationController.get(senderId);
|
||||||
|
if (!sender) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const senderUuid = sender.getUuid();
|
||||||
|
if (!senderUuid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Drop typing indicators for announcement only groups where the sender
|
// Drop typing indicators for announcement only groups where the sender
|
||||||
// is not an admin
|
// is not an admin
|
||||||
if (this.get('announcementsOnly') && !this.isAdmin(senderId)) {
|
if (this.get('announcementsOnly') && !this.isAdmin(senderUuid)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -459,7 +459,12 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
conversationSelector: findAndFormatContact,
|
conversationSelector: findAndFormatContact,
|
||||||
ourConversationId,
|
ourConversationId,
|
||||||
ourNumber: window.textsecure.storage.user.getNumber(),
|
ourNumber: window.textsecure.storage.user.getNumber(),
|
||||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
ourACI: window.textsecure.storage.user
|
||||||
|
.getCheckedUuid(UUIDKind.ACI)
|
||||||
|
.toString(),
|
||||||
|
ourPNI: window.textsecure.storage.user
|
||||||
|
.getCheckedUuid(UUIDKind.PNI)
|
||||||
|
.toString(),
|
||||||
regionCode: window.storage.get('regionCode', 'ZZ'),
|
regionCode: window.storage.get('regionCode', 'ZZ'),
|
||||||
accountSelector: (identifier?: string) => {
|
accountSelector: (identifier?: string) => {
|
||||||
const state = window.reduxStore.getState();
|
const state = window.reduxStore.getState();
|
||||||
|
@ -540,7 +545,12 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
|
|
||||||
const changes = GroupChange.renderChange<string>(change, {
|
const changes = GroupChange.renderChange<string>(change, {
|
||||||
i18n: window.i18n,
|
i18n: window.i18n,
|
||||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
ourACI: window.textsecure.storage.user
|
||||||
|
.getCheckedUuid(UUIDKind.ACI)
|
||||||
|
.toString(),
|
||||||
|
ourPNI: window.textsecure.storage.user
|
||||||
|
.getCheckedUuid(UUIDKind.PNI)
|
||||||
|
.toString(),
|
||||||
renderContact: (conversationId: string) => {
|
renderContact: (conversationId: string) => {
|
||||||
const conversation =
|
const conversation =
|
||||||
window.ConversationController.get(conversationId);
|
window.ConversationController.get(conversationId);
|
||||||
|
@ -2213,12 +2223,14 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
publicParams: initialMessage.groupV2.publicParams,
|
publicParams: initialMessage.groupV2.publicParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Standard GroupV2 modification codepath
|
|
||||||
const existingRevision = conversation.get('revision');
|
const existingRevision = conversation.get('revision');
|
||||||
|
const isFirstUpdate = !_.isNumber(existingRevision);
|
||||||
|
|
||||||
|
// Standard GroupV2 modification codepath
|
||||||
const isV2GroupUpdate =
|
const isV2GroupUpdate =
|
||||||
initialMessage.groupV2 &&
|
initialMessage.groupV2 &&
|
||||||
_.isNumber(initialMessage.groupV2.revision) &&
|
_.isNumber(initialMessage.groupV2.revision) &&
|
||||||
(!_.isNumber(existingRevision) ||
|
(isFirstUpdate ||
|
||||||
initialMessage.groupV2.revision > existingRevision);
|
initialMessage.groupV2.revision > existingRevision);
|
||||||
|
|
||||||
if (isV2GroupUpdate && initialMessage.groupV2) {
|
if (isV2GroupUpdate && initialMessage.groupV2) {
|
||||||
|
@ -2247,9 +2259,9 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ourConversationId =
|
const ourACI = window.textsecure.storage.user.getCheckedUuid(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
UUIDKind.ACI
|
||||||
window.ConversationController.getOurConversationId()!;
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const senderId = window.ConversationController.ensureContactIds({
|
const senderId = window.ConversationController.ensureContactIds({
|
||||||
e164: source,
|
e164: source,
|
||||||
|
@ -2273,15 +2285,17 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const areWeMember =
|
||||||
|
!conversation.get('left') && conversation.hasMember(ourACI);
|
||||||
|
|
||||||
// Drop an incoming GroupV2 message if we or the sender are not part of the group
|
// Drop an incoming GroupV2 message if we or the sender are not part of the group
|
||||||
// after applying the message's associated group changes.
|
// after applying the message's associated group changes.
|
||||||
if (
|
if (
|
||||||
type === 'incoming' &&
|
type === 'incoming' &&
|
||||||
!isDirectConversation(conversation.attributes) &&
|
!isDirectConversation(conversation.attributes) &&
|
||||||
hasGroupV2Prop &&
|
hasGroupV2Prop &&
|
||||||
(conversation.get('left') ||
|
(!areWeMember ||
|
||||||
!conversation.hasMember(ourConversationId) ||
|
(sourceUuid && !conversation.hasMember(new UUID(sourceUuid))))
|
||||||
!conversation.hasMember(senderId))
|
|
||||||
) {
|
) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`Received message destined for group ${conversation.idForLogging()}, which we or the sender are not a part of. Dropping.`
|
`Received message destined for group ${conversation.idForLogging()}, which we or the sender are not a part of. Dropping.`
|
||||||
|
@ -2301,7 +2315,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
!hasGroupV2Prop &&
|
!hasGroupV2Prop &&
|
||||||
!isV1GroupUpdate &&
|
!isV1GroupUpdate &&
|
||||||
conversation.get('members') &&
|
conversation.get('members') &&
|
||||||
(conversation.get('left') || !conversation.hasMember(ourConversationId))
|
!areWeMember
|
||||||
) {
|
) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`Received message destined for group ${conversation.idForLogging()}, which we're not a part of. Dropping.`
|
`Received message destined for group ${conversation.idForLogging()}, which we're not a part of. Dropping.`
|
||||||
|
@ -2323,7 +2337,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
// Drop incoming messages to announcement only groups where sender is not admin
|
// Drop incoming messages to announcement only groups where sender is not admin
|
||||||
if (
|
if (
|
||||||
conversation.get('announcementsOnly') &&
|
conversation.get('announcementsOnly') &&
|
||||||
!conversation.isAdmin(senderId)
|
!conversation.isAdmin(UUID.checkedLookup(senderId))
|
||||||
) {
|
) {
|
||||||
confirm();
|
confirm();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -6,20 +6,76 @@ import PQueue from 'p-queue';
|
||||||
|
|
||||||
import * as log from './logging/log';
|
import * as log from './logging/log';
|
||||||
import { assert } from './util/assert';
|
import { assert } from './util/assert';
|
||||||
|
import { sleep } from './util/sleep';
|
||||||
import { missingCaseError } from './util/missingCaseError';
|
import { missingCaseError } from './util/missingCaseError';
|
||||||
import { isNormalNumber } from './util/isNormalNumber';
|
import { isNormalNumber } from './util/isNormalNumber';
|
||||||
import { take } from './util/iterables';
|
import { take } from './util/iterables';
|
||||||
import { isOlderThan } from './util/timestamp';
|
|
||||||
import type { ConversationModel } from './models/conversations';
|
import type { ConversationModel } from './models/conversations';
|
||||||
import type { StorageInterface } from './types/Storage.d';
|
import type { StorageInterface } from './types/Storage.d';
|
||||||
|
import * as Errors from './types/errors';
|
||||||
import { getProfile } from './util/getProfile';
|
import { getProfile } from './util/getProfile';
|
||||||
import { MINUTE } from './util/durations';
|
import { MINUTE, HOUR, DAY, MONTH } from './util/durations';
|
||||||
|
|
||||||
const STORAGE_KEY = 'lastAttemptedToRefreshProfilesAt';
|
const STORAGE_KEY = 'lastAttemptedToRefreshProfilesAt';
|
||||||
const MAX_AGE_TO_BE_CONSIDERED_ACTIVE = 30 * 24 * 60 * 60 * 1000;
|
const MAX_AGE_TO_BE_CONSIDERED_ACTIVE = MONTH;
|
||||||
const MAX_AGE_TO_BE_CONSIDERED_RECENTLY_REFRESHED = 1 * 24 * 60 * 60 * 1000;
|
const MAX_AGE_TO_BE_CONSIDERED_RECENTLY_REFRESHED = DAY;
|
||||||
const MAX_CONVERSATIONS_TO_REFRESH = 50;
|
const MAX_CONVERSATIONS_TO_REFRESH = 50;
|
||||||
const MIN_ELAPSED_DURATION_TO_REFRESH_AGAIN = 12 * 3600 * 1000;
|
const MIN_ELAPSED_DURATION_TO_REFRESH_AGAIN = 12 * HOUR;
|
||||||
|
const MIN_REFRESH_DELAY = MINUTE;
|
||||||
|
|
||||||
|
export class RoutineProfileRefresher {
|
||||||
|
private interval: NodeJS.Timeout | undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly options: {
|
||||||
|
getAllConversations: () => ReadonlyArray<ConversationModel>;
|
||||||
|
getOurConversationId: () => string | undefined;
|
||||||
|
storage: Pick<StorageInterface, 'get' | 'put'>;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async start(): Promise<void> {
|
||||||
|
if (this.interval !== undefined) {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { storage, getAllConversations, getOurConversationId } = this.options;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
while (true) {
|
||||||
|
const refreshInMs = timeUntilNextRefresh(storage);
|
||||||
|
|
||||||
|
log.info(`routineProfileRefresh: waiting for ${refreshInMs}ms`);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await sleep(refreshInMs);
|
||||||
|
|
||||||
|
const ourConversationId = getOurConversationId();
|
||||||
|
if (!ourConversationId) {
|
||||||
|
log.warn('routineProfileRefresh: missing our conversation id');
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await sleep(MIN_REFRESH_DELAY);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await routineProfileRefresh({
|
||||||
|
allConversations: getAllConversations(),
|
||||||
|
ourConversationId,
|
||||||
|
storage,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
log.error('routineProfileRefresh: failure', Errors.toLogFormat(error));
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await sleep(MIN_REFRESH_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function routineProfileRefresh({
|
export async function routineProfileRefresh({
|
||||||
allConversations,
|
allConversations,
|
||||||
|
@ -29,14 +85,15 @@ export async function routineProfileRefresh({
|
||||||
// Only for tests
|
// Only for tests
|
||||||
getProfileFn = getProfile,
|
getProfileFn = getProfile,
|
||||||
}: {
|
}: {
|
||||||
allConversations: Array<ConversationModel>;
|
allConversations: ReadonlyArray<ConversationModel>;
|
||||||
ourConversationId: string;
|
ourConversationId: string;
|
||||||
storage: Pick<StorageInterface, 'get' | 'put'>;
|
storage: Pick<StorageInterface, 'get' | 'put'>;
|
||||||
getProfileFn?: typeof getProfile;
|
getProfileFn?: typeof getProfile;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
log.info('routineProfileRefresh: starting');
|
log.info('routineProfileRefresh: starting');
|
||||||
|
|
||||||
if (!hasEnoughTimeElapsedSinceLastRefresh(storage)) {
|
const refreshInMs = timeUntilNextRefresh(storage);
|
||||||
|
if (refreshInMs > 0) {
|
||||||
log.info('routineProfileRefresh: too soon to refresh. Doing nothing');
|
log.info('routineProfileRefresh: too soon to refresh. Doing nothing');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -91,24 +148,24 @@ export async function routineProfileRefresh({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasEnoughTimeElapsedSinceLastRefresh(
|
function timeUntilNextRefresh(storage: Pick<StorageInterface, 'get'>): number {
|
||||||
storage: Pick<StorageInterface, 'get'>
|
|
||||||
): boolean {
|
|
||||||
const storedValue = storage.get(STORAGE_KEY);
|
const storedValue = storage.get(STORAGE_KEY);
|
||||||
|
|
||||||
if (isNil(storedValue)) {
|
if (isNil(storedValue)) {
|
||||||
return true;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNormalNumber(storedValue)) {
|
if (isNormalNumber(storedValue)) {
|
||||||
return isOlderThan(storedValue, MIN_ELAPSED_DURATION_TO_REFRESH_AGAIN);
|
const planned = storedValue + MIN_ELAPSED_DURATION_TO_REFRESH_AGAIN;
|
||||||
|
const now = Date.now();
|
||||||
|
return Math.max(0, planned - now);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
false,
|
false,
|
||||||
`An invalid value was stored in ${STORAGE_KEY}; treating it as nil`
|
`An invalid value was stored in ${STORAGE_KEY}; treating it as nil`
|
||||||
);
|
);
|
||||||
return true;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConversationsToRefresh(
|
function getConversationsToRefresh(
|
||||||
|
@ -134,6 +191,20 @@ function* getFilteredConversations(
|
||||||
const type = conversation.get('type');
|
const type = conversation.get('type');
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'private':
|
case 'private':
|
||||||
|
if (
|
||||||
|
conversation.hasProfileKeyCredentialExpired() &&
|
||||||
|
(conversation.id === ourConversationId ||
|
||||||
|
!conversationIdsSeen.has(conversation.id))
|
||||||
|
) {
|
||||||
|
conversation.set({
|
||||||
|
profileKeyCredential: null,
|
||||||
|
profileKeyCredentialExpiration: null,
|
||||||
|
});
|
||||||
|
conversationIdsSeen.add(conversation.id);
|
||||||
|
yield conversation;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!conversationIdsSeen.has(conversation.id) &&
|
!conversationIdsSeen.has(conversation.id) &&
|
||||||
isConversationActive(conversation) &&
|
isConversationActive(conversation) &&
|
||||||
|
|
|
@ -2,14 +2,16 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { last, sortBy } from 'lodash';
|
import { last, sortBy } from 'lodash';
|
||||||
import { AuthCredentialResponse } from '@signalapp/libsignal-client/zkgroup';
|
import { AuthCredentialWithPniResponse } from '@signalapp/libsignal-client/zkgroup';
|
||||||
|
|
||||||
import { getClientZkAuthOperations } from '../util/zkgroup';
|
import { getClientZkAuthOperations } from '../util/zkgroup';
|
||||||
|
|
||||||
import type { GroupCredentialType } from '../textsecure/WebAPI';
|
import type { GroupCredentialType } from '../textsecure/WebAPI';
|
||||||
|
import { strictAssert } from '../util/assert';
|
||||||
import * as durations from '../util/durations';
|
import * as durations from '../util/durations';
|
||||||
import { BackOff } from '../util/BackOff';
|
import { BackOff } from '../util/BackOff';
|
||||||
import { sleep } from '../util/sleep';
|
import { sleep } from '../util/sleep';
|
||||||
|
import { toDayMillis } from '../util/timestamp';
|
||||||
import { UUIDKind } from '../types/UUID';
|
import { UUIDKind } from '../types/UUID';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
|
|
||||||
|
@ -17,20 +19,25 @@ export const GROUP_CREDENTIALS_KEY = 'groupCredentials';
|
||||||
|
|
||||||
type CredentialsDataType = Array<GroupCredentialType>;
|
type CredentialsDataType = Array<GroupCredentialType>;
|
||||||
type RequestDatesType = {
|
type RequestDatesType = {
|
||||||
startDay: number;
|
startDayInMs: number;
|
||||||
endDay: number;
|
endDayInMs: number;
|
||||||
};
|
};
|
||||||
type NextCredentialsType = {
|
type NextCredentialsType = {
|
||||||
today: GroupCredentialType;
|
today: GroupCredentialType;
|
||||||
tomorrow: GroupCredentialType;
|
tomorrow: GroupCredentialType;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getTodayInEpoch() {
|
|
||||||
return Math.floor(Date.now() / durations.DAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
let started = false;
|
let started = false;
|
||||||
|
|
||||||
|
function getCheckedCredentials(reason: string): CredentialsDataType {
|
||||||
|
const result = window.storage.get('groupCredentials');
|
||||||
|
strictAssert(
|
||||||
|
result !== undefined,
|
||||||
|
`getCheckedCredentials: no credentials found, ${reason}`
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export async function initializeGroupCredentialFetcher(): Promise<void> {
|
export async function initializeGroupCredentialFetcher(): Promise<void> {
|
||||||
if (started) {
|
if (started) {
|
||||||
return;
|
return;
|
||||||
|
@ -89,16 +96,14 @@ export async function runWithRetry(
|
||||||
}
|
}
|
||||||
|
|
||||||
// In cases where we are at a day boundary, we might need to use tomorrow in a retry
|
// In cases where we are at a day boundary, we might need to use tomorrow in a retry
|
||||||
export function getCredentialsForToday(
|
export function getCheckedCredentialsForToday(
|
||||||
data: CredentialsDataType | undefined
|
reason: string
|
||||||
): NextCredentialsType {
|
): NextCredentialsType {
|
||||||
if (!data) {
|
const data = getCheckedCredentials(reason);
|
||||||
throw new Error('getCredentialsForToday: No credentials fetched!');
|
|
||||||
}
|
|
||||||
|
|
||||||
const todayInEpoch = getTodayInEpoch();
|
const today = toDayMillis(Date.now());
|
||||||
const todayIndex = data.findIndex(
|
const todayIndex = data.findIndex(
|
||||||
(item: GroupCredentialType) => item.redemptionTime === todayInEpoch
|
(item: GroupCredentialType) => item.redemptionTime === today
|
||||||
);
|
);
|
||||||
if (todayIndex < 0) {
|
if (todayIndex < 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -113,29 +118,37 @@ export function getCredentialsForToday(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function maybeFetchNewCredentials(): Promise<void> {
|
export async function maybeFetchNewCredentials(): Promise<void> {
|
||||||
const uuid = window.textsecure.storage.user.getUuid()?.toString();
|
const logId = 'maybeFetchNewCredentials';
|
||||||
if (!uuid) {
|
|
||||||
log.info('maybeFetchCredentials: no UUID, returning early');
|
const aci = window.textsecure.storage.user.getUuid(UUIDKind.ACI)?.toString();
|
||||||
|
if (!aci) {
|
||||||
|
log.info(`${logId}: no ACI, returning early`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const previous: CredentialsDataType | undefined = window.storage.get(
|
|
||||||
GROUP_CREDENTIALS_KEY
|
const pni = window.textsecure.storage.user.getUuid(UUIDKind.PNI)?.toString();
|
||||||
);
|
if (!pni) {
|
||||||
|
log.info(`${logId}: no PNI, returning early`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previous: CredentialsDataType | undefined =
|
||||||
|
window.storage.get('groupCredentials');
|
||||||
const requestDates = getDatesForRequest(previous);
|
const requestDates = getDatesForRequest(previous);
|
||||||
if (!requestDates) {
|
if (!requestDates) {
|
||||||
log.info('maybeFetchCredentials: no new credentials needed');
|
log.info(`${logId}: no new credentials needed`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountManager = window.getAccountManager();
|
const { server } = window.textsecure;
|
||||||
if (!accountManager) {
|
if (!server) {
|
||||||
log.info('maybeFetchCredentials: unable to get AccountManager');
|
log.error(`${logId}: unable to get server`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { startDay, endDay } = requestDates;
|
const { startDayInMs, endDayInMs } = requestDates;
|
||||||
log.info(
|
log.info(
|
||||||
`maybeFetchCredentials: fetching credentials for ${startDay} through ${endDay}`
|
`${logId}: fetching credentials for ${startDayInMs} through ${endDayInMs}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const serverPublicParamsBase64 = window.getServerPublicParams();
|
const serverPublicParamsBase64 = window.getServerPublicParams();
|
||||||
|
@ -143,46 +156,47 @@ export async function maybeFetchNewCredentials(): Promise<void> {
|
||||||
serverPublicParamsBase64
|
serverPublicParamsBase64
|
||||||
);
|
);
|
||||||
const newCredentials = sortCredentials(
|
const newCredentials = sortCredentials(
|
||||||
await accountManager.getGroupCredentials(startDay, endDay, UUIDKind.ACI)
|
await server.getGroupCredentials({ startDayInMs, endDayInMs })
|
||||||
).map((item: GroupCredentialType) => {
|
).map((item: GroupCredentialType) => {
|
||||||
const authCredential = clientZKAuthOperations.receiveAuthCredential(
|
const authCredential = clientZKAuthOperations.receiveAuthCredentialWithPni(
|
||||||
uuid,
|
aci,
|
||||||
|
pni,
|
||||||
item.redemptionTime,
|
item.redemptionTime,
|
||||||
new AuthCredentialResponse(Buffer.from(item.credential, 'base64'))
|
new AuthCredentialWithPniResponse(Buffer.from(item.credential, 'base64'))
|
||||||
);
|
);
|
||||||
const credential = authCredential.serialize().toString('base64');
|
const credential = authCredential.serialize().toString('base64');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
redemptionTime: item.redemptionTime,
|
redemptionTime: item.redemptionTime * durations.SECOND,
|
||||||
credential,
|
credential,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const todayInEpoch = getTodayInEpoch();
|
const today = toDayMillis(Date.now());
|
||||||
const previousCleaned = previous
|
const previousCleaned = previous
|
||||||
? previous.filter(
|
? previous.filter(
|
||||||
(item: GroupCredentialType) => item.redemptionTime >= todayInEpoch
|
(item: GroupCredentialType) => item.redemptionTime >= today
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
const finalCredentials = [...previousCleaned, ...newCredentials];
|
const finalCredentials = [...previousCleaned, ...newCredentials];
|
||||||
|
|
||||||
log.info('maybeFetchCredentials: Saving new credentials...');
|
log.info(`${logId}: Saving new credentials...`);
|
||||||
// Note: we don't wait for this to finish
|
// Note: we don't wait for this to finish
|
||||||
window.storage.put(GROUP_CREDENTIALS_KEY, finalCredentials);
|
window.storage.put('groupCredentials', finalCredentials);
|
||||||
log.info('maybeFetchCredentials: Save complete.');
|
log.info(`${logId}: Save complete.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDatesForRequest(
|
export function getDatesForRequest(
|
||||||
data?: CredentialsDataType
|
data?: CredentialsDataType
|
||||||
): RequestDatesType | undefined {
|
): RequestDatesType | undefined {
|
||||||
const todayInEpoch = getTodayInEpoch();
|
const today = toDayMillis(Date.now());
|
||||||
const oneWeekOut = todayInEpoch + 7;
|
const oneWeekOut = today + durations.WEEK;
|
||||||
|
|
||||||
const lastCredential = last(data);
|
const lastCredential = last(data);
|
||||||
if (!lastCredential || lastCredential.redemptionTime < todayInEpoch) {
|
if (!lastCredential || lastCredential.redemptionTime < today) {
|
||||||
return {
|
return {
|
||||||
startDay: todayInEpoch,
|
startDayInMs: today,
|
||||||
endDay: oneWeekOut,
|
endDayInMs: oneWeekOut,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,8 +205,8 @@ export function getDatesForRequest(
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
startDay: lastCredential.redemptionTime + 1,
|
startDayInMs: lastCredential.redemptionTime + durations.DAY,
|
||||||
endDay: oneWeekOut,
|
endDayInMs: oneWeekOut,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -205,6 +205,7 @@ const dataInterface: ClientInterface = {
|
||||||
updateConversations,
|
updateConversations,
|
||||||
removeConversation,
|
removeConversation,
|
||||||
updateAllConversationColors,
|
updateAllConversationColors,
|
||||||
|
removeAllProfileKeyCredentials,
|
||||||
|
|
||||||
getAllConversations,
|
getAllConversations,
|
||||||
getAllConversationIds,
|
getAllConversationIds,
|
||||||
|
@ -1161,7 +1162,7 @@ async function getMessageBySender({
|
||||||
sent_at,
|
sent_at,
|
||||||
}: {
|
}: {
|
||||||
source: string;
|
source: string;
|
||||||
sourceUuid: string;
|
sourceUuid: UUIDStringType;
|
||||||
sourceDevice: number;
|
sourceDevice: number;
|
||||||
sent_at: number;
|
sent_at: number;
|
||||||
}) {
|
}) {
|
||||||
|
@ -1271,7 +1272,7 @@ async function getOlderStories(options: {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
receivedAt?: number;
|
receivedAt?: number;
|
||||||
sentAt?: number;
|
sentAt?: number;
|
||||||
sourceUuid?: string;
|
sourceUuid?: UUIDStringType;
|
||||||
}): Promise<Array<MessageType>> {
|
}): Promise<Array<MessageType>> {
|
||||||
return channels.getOlderStories(options);
|
return channels.getOlderStories(options);
|
||||||
}
|
}
|
||||||
|
@ -1794,6 +1795,10 @@ async function updateAllConversationColors(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function removeAllProfileKeyCredentials(): Promise<void> {
|
||||||
|
return channels.removeAllProfileKeyCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
function getMaxMessageCounter(): Promise<number | undefined> {
|
function getMaxMessageCounter(): Promise<number | undefined> {
|
||||||
return channels.getMaxMessageCounter();
|
return channels.getMaxMessageCounter();
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,7 +202,7 @@ export type UnprocessedType = {
|
||||||
|
|
||||||
messageAgeSec?: number;
|
messageAgeSec?: number;
|
||||||
source?: string;
|
source?: string;
|
||||||
sourceUuid?: string;
|
sourceUuid?: UUIDStringType;
|
||||||
sourceDevice?: number;
|
sourceDevice?: number;
|
||||||
destinationUuid?: string;
|
destinationUuid?: string;
|
||||||
serverGuid?: string;
|
serverGuid?: string;
|
||||||
|
@ -213,7 +213,7 @@ export type UnprocessedType = {
|
||||||
|
|
||||||
export type UnprocessedUpdateType = {
|
export type UnprocessedUpdateType = {
|
||||||
source?: string;
|
source?: string;
|
||||||
sourceUuid?: string;
|
sourceUuid?: UUIDStringType;
|
||||||
sourceDevice?: number;
|
sourceDevice?: number;
|
||||||
serverGuid?: string;
|
serverGuid?: string;
|
||||||
serverTimestamp?: number;
|
serverTimestamp?: number;
|
||||||
|
@ -257,8 +257,8 @@ export type StoryDistributionWithMembersType = Readonly<
|
||||||
|
|
||||||
export type StoryReadType = Readonly<{
|
export type StoryReadType = Readonly<{
|
||||||
authorId: UUIDStringType;
|
authorId: UUIDStringType;
|
||||||
conversationId: UUIDStringType;
|
conversationId: string;
|
||||||
storyId: UUIDStringType;
|
storyId: string;
|
||||||
storyReadDate: number;
|
storyReadDate: number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
@ -362,6 +362,7 @@ export type DataInterface = {
|
||||||
value: CustomColorType;
|
value: CustomColorType;
|
||||||
}
|
}
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
removeAllProfileKeyCredentials: () => Promise<void>;
|
||||||
|
|
||||||
getAllConversations: () => Promise<Array<ConversationType>>;
|
getAllConversations: () => Promise<Array<ConversationType>>;
|
||||||
getAllConversationIds: () => Promise<Array<string>>;
|
getAllConversationIds: () => Promise<Array<string>>;
|
||||||
|
@ -439,7 +440,7 @@ export type DataInterface = {
|
||||||
_removeAllReactions: () => Promise<void>;
|
_removeAllReactions: () => Promise<void>;
|
||||||
getMessageBySender: (options: {
|
getMessageBySender: (options: {
|
||||||
source: string;
|
source: string;
|
||||||
sourceUuid: string;
|
sourceUuid: UUIDStringType;
|
||||||
sourceDevice: number;
|
sourceDevice: number;
|
||||||
sent_at: number;
|
sent_at: number;
|
||||||
}) => Promise<MessageType | undefined>;
|
}) => Promise<MessageType | undefined>;
|
||||||
|
@ -462,7 +463,7 @@ export type DataInterface = {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
receivedAt?: number;
|
receivedAt?: number;
|
||||||
sentAt?: number;
|
sentAt?: number;
|
||||||
sourceUuid?: string;
|
sourceUuid?: UUIDStringType;
|
||||||
}) => Promise<Array<MessageType>>;
|
}) => Promise<Array<MessageType>>;
|
||||||
// getNewerMessagesByConversation is JSON on server, full message on Client
|
// getNewerMessagesByConversation is JSON on server, full message on Client
|
||||||
getMessageMetricsForConversation: (
|
getMessageMetricsForConversation: (
|
||||||
|
|
|
@ -200,6 +200,7 @@ const dataInterface: ServerInterface = {
|
||||||
updateConversations,
|
updateConversations,
|
||||||
removeConversation,
|
removeConversation,
|
||||||
updateAllConversationColors,
|
updateAllConversationColors,
|
||||||
|
removeAllProfileKeyCredentials,
|
||||||
|
|
||||||
getAllConversations,
|
getAllConversations,
|
||||||
getAllConversationIds,
|
getAllConversationIds,
|
||||||
|
@ -2033,7 +2034,7 @@ async function getMessageBySender({
|
||||||
sent_at,
|
sent_at,
|
||||||
}: {
|
}: {
|
||||||
source: string;
|
source: string;
|
||||||
sourceUuid: string;
|
sourceUuid: UUIDStringType;
|
||||||
sourceDevice: number;
|
sourceDevice: number;
|
||||||
sent_at: number;
|
sent_at: number;
|
||||||
}): Promise<MessageType | undefined> {
|
}): Promise<MessageType | undefined> {
|
||||||
|
@ -2443,7 +2444,7 @@ async function getOlderStories({
|
||||||
limit?: number;
|
limit?: number;
|
||||||
receivedAt?: number;
|
receivedAt?: number;
|
||||||
sentAt?: number;
|
sentAt?: number;
|
||||||
sourceUuid?: string;
|
sourceUuid?: UUIDStringType;
|
||||||
}): Promise<Array<MessageType>> {
|
}): Promise<Array<MessageType>> {
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
const rows: JSONRows = db
|
const rows: JSONRows = db
|
||||||
|
@ -5067,3 +5068,15 @@ async function updateAllConversationColors(
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function removeAllProfileKeyCredentials(): Promise<void> {
|
||||||
|
const db = getInstance();
|
||||||
|
|
||||||
|
db.exec(
|
||||||
|
`
|
||||||
|
UPDATE conversations
|
||||||
|
SET
|
||||||
|
json = json_remove(json, '$.profileKeyCredential')
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -852,7 +852,7 @@ function groupCallStateChange(
|
||||||
didSomeoneStartPresenting = false;
|
didSomeoneStartPresenting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { ourUuid } = getState().user;
|
const { ourACI: ourUuid } = getState().user;
|
||||||
strictAssert(ourUuid, 'groupCallStateChange failed to fetch our uuid');
|
strictAssert(ourUuid, 'groupCallStateChange failed to fetch our uuid');
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { DAY } from '../../util/durations';
|
||||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||||
import { StoryViewDirectionType, StoryViewModeType } from '../../types/Stories';
|
import { StoryViewDirectionType, StoryViewModeType } from '../../types/Stories';
|
||||||
import { ToastReactionFailed } from '../../components/ToastReactionFailed';
|
import { ToastReactionFailed } from '../../components/ToastReactionFailed';
|
||||||
import { UUID } from '../../types/UUID';
|
|
||||||
import { enqueueReactionForSend } from '../../reactions/enqueueReactionForSend';
|
import { enqueueReactionForSend } from '../../reactions/enqueueReactionForSend';
|
||||||
import { getMessageById } from '../../messages/getMessageById';
|
import { getMessageById } from '../../messages/getMessageById';
|
||||||
import { markViewed } from '../../services/MessageUpdater';
|
import { markViewed } from '../../services/MessageUpdater';
|
||||||
|
@ -271,7 +270,7 @@ function markStoryRead(
|
||||||
await dataInterface.addNewStoryRead({
|
await dataInterface.addNewStoryRead({
|
||||||
authorId: message.attributes.sourceUuid,
|
authorId: message.attributes.sourceUuid,
|
||||||
conversationId: message.attributes.conversationId,
|
conversationId: message.attributes.conversationId,
|
||||||
storyId: UUID.fromString(messageId),
|
storyId: messageId,
|
||||||
storyReadDate,
|
storyReadDate,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,8 @@ export type UserStateType = {
|
||||||
tempPath: string;
|
tempPath: string;
|
||||||
ourConversationId: string | undefined;
|
ourConversationId: string | undefined;
|
||||||
ourDeviceId: number | undefined;
|
ourDeviceId: number | undefined;
|
||||||
ourUuid: UUIDStringType | undefined;
|
ourACI: UUIDStringType | undefined;
|
||||||
|
ourPNI: UUIDStringType | undefined;
|
||||||
ourNumber: string | undefined;
|
ourNumber: string | undefined;
|
||||||
platform: string;
|
platform: string;
|
||||||
regionCode: string | undefined;
|
regionCode: string | undefined;
|
||||||
|
@ -39,7 +40,8 @@ type UserChangedActionType = {
|
||||||
payload: {
|
payload: {
|
||||||
ourConversationId?: string;
|
ourConversationId?: string;
|
||||||
ourDeviceId?: number;
|
ourDeviceId?: number;
|
||||||
ourUuid?: UUIDStringType;
|
ourACI?: UUIDStringType;
|
||||||
|
ourPNI?: UUIDStringType;
|
||||||
ourNumber?: string;
|
ourNumber?: string;
|
||||||
regionCode?: string;
|
regionCode?: string;
|
||||||
interactionMode?: 'mouse' | 'keyboard';
|
interactionMode?: 'mouse' | 'keyboard';
|
||||||
|
@ -64,7 +66,8 @@ function userChanged(attributes: {
|
||||||
ourConversationId?: string;
|
ourConversationId?: string;
|
||||||
ourDeviceId?: number;
|
ourDeviceId?: number;
|
||||||
ourNumber?: string;
|
ourNumber?: string;
|
||||||
ourUuid?: UUIDStringType;
|
ourACI?: UUIDStringType;
|
||||||
|
ourPNI?: UUIDStringType;
|
||||||
regionCode?: string;
|
regionCode?: string;
|
||||||
theme?: ThemeType;
|
theme?: ThemeType;
|
||||||
isMainWindowMaximized?: boolean;
|
isMainWindowMaximized?: boolean;
|
||||||
|
@ -95,7 +98,8 @@ export function getEmptyState(): UserStateType {
|
||||||
tempPath: 'missing',
|
tempPath: 'missing',
|
||||||
ourConversationId: 'missing',
|
ourConversationId: 'missing',
|
||||||
ourDeviceId: 0,
|
ourDeviceId: 0,
|
||||||
ourUuid: '00000000-0000-4000-8000-000000000000',
|
ourACI: undefined,
|
||||||
|
ourPNI: undefined,
|
||||||
ourNumber: 'missing',
|
ourNumber: 'missing',
|
||||||
regionCode: 'missing',
|
regionCode: 'missing',
|
||||||
platform: 'missing',
|
platform: 'missing',
|
||||||
|
|
|
@ -28,6 +28,7 @@ import type { StoryDataType } from './ducks/stories';
|
||||||
import type { StoryDistributionListDataType } from './ducks/storyDistributionLists';
|
import type { StoryDistributionListDataType } from './ducks/storyDistributionLists';
|
||||||
import { getInitialState as stickers } from '../types/Stickers';
|
import { getInitialState as stickers } from '../types/Stickers';
|
||||||
import type { MenuOptionsType } from '../types/menu';
|
import type { MenuOptionsType } from '../types/menu';
|
||||||
|
import { UUIDKind } from '../types/UUID';
|
||||||
import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis';
|
import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis';
|
||||||
import type { MainWindowStatsType } from '../windows/context';
|
import type { MainWindowStatsType } from '../windows/context';
|
||||||
|
|
||||||
|
@ -51,7 +52,12 @@ export function getInitialState({
|
||||||
conversation.format()
|
conversation.format()
|
||||||
);
|
);
|
||||||
const ourNumber = window.textsecure.storage.user.getNumber();
|
const ourNumber = window.textsecure.storage.user.getNumber();
|
||||||
const ourUuid = window.textsecure.storage.user.getUuid()?.toString();
|
const ourACI = window.textsecure.storage.user
|
||||||
|
.getUuid(UUIDKind.ACI)
|
||||||
|
?.toString();
|
||||||
|
const ourPNI = window.textsecure.storage.user
|
||||||
|
.getUuid(UUIDKind.PNI)
|
||||||
|
?.toString();
|
||||||
const ourConversationId =
|
const ourConversationId =
|
||||||
window.ConversationController.getOurConversationId();
|
window.ConversationController.getOurConversationId();
|
||||||
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
||||||
|
@ -119,7 +125,8 @@ export function getInitialState({
|
||||||
ourConversationId,
|
ourConversationId,
|
||||||
ourDeviceId,
|
ourDeviceId,
|
||||||
ourNumber,
|
ourNumber,
|
||||||
ourUuid,
|
ourACI,
|
||||||
|
ourPNI,
|
||||||
platform: window.platform,
|
platform: window.platform,
|
||||||
i18n: window.i18n,
|
i18n: window.i18n,
|
||||||
localeMessages: window.SignalContext.localeMessages,
|
localeMessages: window.SignalContext.localeMessages,
|
||||||
|
|
|
@ -12,7 +12,7 @@ import type {
|
||||||
GroupCallStateType,
|
GroupCallStateType,
|
||||||
} from '../ducks/calling';
|
} from '../ducks/calling';
|
||||||
import { getIncomingCall as getIncomingCallHelper } from '../ducks/calling';
|
import { getIncomingCall as getIncomingCallHelper } from '../ducks/calling';
|
||||||
import { getUserUuid } from './user';
|
import { getUserACI } from './user';
|
||||||
import { getOwn } from '../../util/getOwn';
|
import { getOwn } from '../../util/getOwn';
|
||||||
import { CallViewMode } from '../../types/Calling';
|
import { CallViewMode } from '../../types/Calling';
|
||||||
import type { UUIDStringType } from '../../types/UUID';
|
import type { UUIDStringType } from '../../types/UUID';
|
||||||
|
@ -61,7 +61,7 @@ export const isInCall = createSelector(
|
||||||
|
|
||||||
export const getIncomingCall = createSelector(
|
export const getIncomingCall = createSelector(
|
||||||
getCallsByConversation,
|
getCallsByConversation,
|
||||||
getUserUuid,
|
getUserACI,
|
||||||
(
|
(
|
||||||
callsByConversation: CallsByConversationType,
|
callsByConversation: CallsByConversationType,
|
||||||
ourUuid: UUIDStringType | undefined
|
ourUuid: UUIDStringType | undefined
|
||||||
|
|
|
@ -49,7 +49,8 @@ import {
|
||||||
getRegionCode,
|
getRegionCode,
|
||||||
getUserConversationId,
|
getUserConversationId,
|
||||||
getUserNumber,
|
getUserNumber,
|
||||||
getUserUuid,
|
getUserACI,
|
||||||
|
getUserPNI,
|
||||||
} from './user';
|
} from './user';
|
||||||
import { getPinnedConversationIds } from './items';
|
import { getPinnedConversationIds } from './items';
|
||||||
import { getPropsForBubble } from './message';
|
import { getPropsForBubble } from './message';
|
||||||
|
@ -780,7 +781,8 @@ export const getMessageSelector = createSelector(
|
||||||
getConversationSelector,
|
getConversationSelector,
|
||||||
getRegionCode,
|
getRegionCode,
|
||||||
getUserNumber,
|
getUserNumber,
|
||||||
getUserUuid,
|
getUserACI,
|
||||||
|
getUserPNI,
|
||||||
getUserConversationId,
|
getUserConversationId,
|
||||||
getCallSelector,
|
getCallSelector,
|
||||||
getActiveCall,
|
getActiveCall,
|
||||||
|
@ -793,7 +795,8 @@ export const getMessageSelector = createSelector(
|
||||||
conversationSelector: GetConversationByIdType,
|
conversationSelector: GetConversationByIdType,
|
||||||
regionCode: string | undefined,
|
regionCode: string | undefined,
|
||||||
ourNumber: string | undefined,
|
ourNumber: string | undefined,
|
||||||
ourUuid: UUIDStringType | undefined,
|
ourACI: UUIDStringType | undefined,
|
||||||
|
ourPNI: UUIDStringType | undefined,
|
||||||
ourConversationId: string | undefined,
|
ourConversationId: string | undefined,
|
||||||
callSelector: CallSelectorType,
|
callSelector: CallSelectorType,
|
||||||
activeCall: undefined | CallStateType,
|
activeCall: undefined | CallStateType,
|
||||||
|
@ -810,7 +813,8 @@ export const getMessageSelector = createSelector(
|
||||||
conversationSelector,
|
conversationSelector,
|
||||||
ourConversationId,
|
ourConversationId,
|
||||||
ourNumber,
|
ourNumber,
|
||||||
ourUuid,
|
ourACI,
|
||||||
|
ourPNI,
|
||||||
regionCode,
|
regionCode,
|
||||||
selectedMessageId: selectedMessage?.id,
|
selectedMessageId: selectedMessage?.id,
|
||||||
selectedMessageCounter: selectedMessage?.counter,
|
selectedMessageCounter: selectedMessage?.counter,
|
||||||
|
|
|
@ -61,7 +61,8 @@ import {
|
||||||
getRegionCode,
|
getRegionCode,
|
||||||
getUserConversationId,
|
getUserConversationId,
|
||||||
getUserNumber,
|
getUserNumber,
|
||||||
getUserUuid,
|
getUserACI,
|
||||||
|
getUserPNI,
|
||||||
} from './user';
|
} from './user';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
@ -114,7 +115,8 @@ export type GetPropsForBubbleOptions = Readonly<{
|
||||||
conversationSelector: GetConversationByIdType;
|
conversationSelector: GetConversationByIdType;
|
||||||
ourConversationId?: string;
|
ourConversationId?: string;
|
||||||
ourNumber?: string;
|
ourNumber?: string;
|
||||||
ourUuid?: UUIDStringType;
|
ourACI?: UUIDStringType;
|
||||||
|
ourPNI?: UUIDStringType;
|
||||||
selectedMessageId?: string;
|
selectedMessageId?: string;
|
||||||
selectedMessageCounter?: number;
|
selectedMessageCounter?: number;
|
||||||
regionCode?: string;
|
regionCode?: string;
|
||||||
|
@ -182,7 +184,7 @@ export function getSourceDevice(
|
||||||
|
|
||||||
export function getSourceUuid(
|
export function getSourceUuid(
|
||||||
message: MessageWithUIFieldsType,
|
message: MessageWithUIFieldsType,
|
||||||
ourUuid: string | undefined
|
ourACI: string | undefined
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (isIncoming(message)) {
|
if (isIncoming(message)) {
|
||||||
return message.sourceUuid;
|
return message.sourceUuid;
|
||||||
|
@ -193,12 +195,16 @@ export function getSourceUuid(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ourUuid;
|
return ourACI;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetContactOptions = Pick<
|
export type GetContactOptions = Pick<
|
||||||
GetPropsForBubbleOptions,
|
GetPropsForBubbleOptions,
|
||||||
'conversationSelector' | 'ourConversationId' | 'ourNumber' | 'ourUuid'
|
| 'conversationSelector'
|
||||||
|
| 'ourConversationId'
|
||||||
|
| 'ourNumber'
|
||||||
|
| 'ourACI'
|
||||||
|
| 'ourPNI'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function getContactId(
|
export function getContactId(
|
||||||
|
@ -207,11 +213,11 @@ export function getContactId(
|
||||||
conversationSelector,
|
conversationSelector,
|
||||||
ourConversationId,
|
ourConversationId,
|
||||||
ourNumber,
|
ourNumber,
|
||||||
ourUuid,
|
ourACI,
|
||||||
}: GetContactOptions
|
}: GetContactOptions
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
const source = getSource(message, ourNumber);
|
const source = getSource(message, ourNumber);
|
||||||
const sourceUuid = getSourceUuid(message, ourUuid);
|
const sourceUuid = getSourceUuid(message, ourACI);
|
||||||
|
|
||||||
if (!source && !sourceUuid) {
|
if (!source && !sourceUuid) {
|
||||||
return ourConversationId;
|
return ourConversationId;
|
||||||
|
@ -228,11 +234,11 @@ export function getContact(
|
||||||
conversationSelector,
|
conversationSelector,
|
||||||
ourConversationId,
|
ourConversationId,
|
||||||
ourNumber,
|
ourNumber,
|
||||||
ourUuid,
|
ourACI,
|
||||||
}: GetContactOptions
|
}: GetContactOptions
|
||||||
): ConversationType {
|
): ConversationType {
|
||||||
const source = getSource(message, ourNumber);
|
const source = getSource(message, ourNumber);
|
||||||
const sourceUuid = getSourceUuid(message, ourUuid);
|
const sourceUuid = getSourceUuid(message, ourACI);
|
||||||
|
|
||||||
if (!source && !sourceUuid) {
|
if (!source && !sourceUuid) {
|
||||||
return conversationSelector(ourConversationId);
|
return conversationSelector(ourConversationId);
|
||||||
|
@ -563,7 +569,8 @@ export type GetPropsForMessageOptions = Pick<
|
||||||
GetPropsForBubbleOptions,
|
GetPropsForBubbleOptions,
|
||||||
| 'conversationSelector'
|
| 'conversationSelector'
|
||||||
| 'ourConversationId'
|
| 'ourConversationId'
|
||||||
| 'ourUuid'
|
| 'ourACI'
|
||||||
|
| 'ourPNI'
|
||||||
| 'ourNumber'
|
| 'ourNumber'
|
||||||
| 'selectedMessageId'
|
| 'selectedMessageId'
|
||||||
| 'selectedMessageCounter'
|
| 'selectedMessageCounter'
|
||||||
|
@ -621,7 +628,7 @@ const getShallowPropsForMessage = createSelectorCreator(memoizeByRoot, isEqual)(
|
||||||
conversationSelector,
|
conversationSelector,
|
||||||
ourConversationId,
|
ourConversationId,
|
||||||
ourNumber,
|
ourNumber,
|
||||||
ourUuid,
|
ourACI,
|
||||||
regionCode,
|
regionCode,
|
||||||
selectedMessageId,
|
selectedMessageId,
|
||||||
selectedMessageCounter,
|
selectedMessageCounter,
|
||||||
|
@ -652,7 +659,7 @@ const getShallowPropsForMessage = createSelectorCreator(memoizeByRoot, isEqual)(
|
||||||
conversationSelector,
|
conversationSelector,
|
||||||
ourConversationId,
|
ourConversationId,
|
||||||
ourNumber,
|
ourNumber,
|
||||||
ourUuid,
|
ourACI,
|
||||||
});
|
});
|
||||||
const contactNameColor = contactNameColorSelector(conversationId, authorId);
|
const contactNameColor = contactNameColorSelector(conversationId, authorId);
|
||||||
|
|
||||||
|
@ -781,7 +788,8 @@ export const getPropsForMessage: (
|
||||||
export const getMessagePropsSelector = createSelector(
|
export const getMessagePropsSelector = createSelector(
|
||||||
getConversationSelector,
|
getConversationSelector,
|
||||||
getUserConversationId,
|
getUserConversationId,
|
||||||
getUserUuid,
|
getUserACI,
|
||||||
|
getUserPNI,
|
||||||
getUserNumber,
|
getUserNumber,
|
||||||
getRegionCode,
|
getRegionCode,
|
||||||
getAccountSelector,
|
getAccountSelector,
|
||||||
|
@ -790,7 +798,8 @@ export const getMessagePropsSelector = createSelector(
|
||||||
(
|
(
|
||||||
conversationSelector,
|
conversationSelector,
|
||||||
ourConversationId,
|
ourConversationId,
|
||||||
ourUuid,
|
ourACI,
|
||||||
|
ourPNI,
|
||||||
ourNumber,
|
ourNumber,
|
||||||
regionCode,
|
regionCode,
|
||||||
accountSelector,
|
accountSelector,
|
||||||
|
@ -804,7 +813,8 @@ export const getMessagePropsSelector = createSelector(
|
||||||
conversationSelector,
|
conversationSelector,
|
||||||
ourConversationId,
|
ourConversationId,
|
||||||
ourNumber,
|
ourNumber,
|
||||||
ourUuid,
|
ourACI,
|
||||||
|
ourPNI,
|
||||||
regionCode,
|
regionCode,
|
||||||
selectedMessageCounter: selectedMessage?.counter,
|
selectedMessageCounter: selectedMessage?.counter,
|
||||||
selectedMessageId: selectedMessage?.id,
|
selectedMessageId: selectedMessage?.id,
|
||||||
|
@ -977,7 +987,7 @@ export function isGroupV2Change(message: MessageWithUIFieldsType): boolean {
|
||||||
|
|
||||||
function getPropsForGroupV2Change(
|
function getPropsForGroupV2Change(
|
||||||
message: MessageWithUIFieldsType,
|
message: MessageWithUIFieldsType,
|
||||||
{ conversationSelector, ourUuid }: GetPropsForBubbleOptions
|
{ conversationSelector, ourACI, ourPNI }: GetPropsForBubbleOptions
|
||||||
): GroupsV2Props {
|
): GroupsV2Props {
|
||||||
const change = message.groupV2Change;
|
const change = message.groupV2Change;
|
||||||
|
|
||||||
|
@ -992,7 +1002,8 @@ function getPropsForGroupV2Change(
|
||||||
groupName: conversation?.type === 'group' ? conversation?.name : undefined,
|
groupName: conversation?.type === 'group' ? conversation?.name : undefined,
|
||||||
groupMemberships: conversation.memberships,
|
groupMemberships: conversation.memberships,
|
||||||
groupBannedMemberships: conversation.bannedMemberships,
|
groupBannedMemberships: conversation.bannedMemberships,
|
||||||
ourUuid,
|
ourACI,
|
||||||
|
ourPNI,
|
||||||
change,
|
change,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,9 +35,14 @@ export const getUserConversationId = createSelector(
|
||||||
(state: UserStateType): string | undefined => state.ourConversationId
|
(state: UserStateType): string | undefined => state.ourConversationId
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getUserUuid = createSelector(
|
export const getUserACI = createSelector(
|
||||||
getUser,
|
getUser,
|
||||||
(state: UserStateType): UUIDStringType | undefined => state.ourUuid
|
(state: UserStateType): UUIDStringType | undefined => state.ourACI
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getUserPNI = createSelector(
|
||||||
|
getUser,
|
||||||
|
(state: UserStateType): UUIDStringType | undefined => state.ourPNI
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getIntl = createSelector(
|
export const getIntl = createSelector(
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { CallMode } from '../../types/Calling';
|
||||||
import type { ConversationType } from '../ducks/conversations';
|
import type { ConversationType } from '../ducks/conversations';
|
||||||
import { getConversationCallMode } from '../ducks/conversations';
|
import { getConversationCallMode } from '../ducks/conversations';
|
||||||
import { getActiveCall, isAnybodyElseInGroupCall } from '../ducks/calling';
|
import { getActiveCall, isAnybodyElseInGroupCall } from '../ducks/calling';
|
||||||
import { getUserUuid, getIntl, getTheme } from '../selectors/user';
|
import { getUserACI, getIntl, getTheme } from '../selectors/user';
|
||||||
import { getOwn } from '../../util/getOwn';
|
import { getOwn } from '../../util/getOwn';
|
||||||
import { missingCaseError } from '../../util/missingCaseError';
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
import { isConversationSMSOnly } from '../../util/isConversationSMSOnly';
|
import { isConversationSMSOnly } from '../../util/isConversationSMSOnly';
|
||||||
|
@ -47,8 +47,8 @@ const getOutgoingCallButtonStyle = (
|
||||||
state: StateType
|
state: StateType
|
||||||
): OutgoingCallButtonStyle => {
|
): OutgoingCallButtonStyle => {
|
||||||
const { calling } = state;
|
const { calling } = state;
|
||||||
const ourUuid = getUserUuid(state);
|
const ourACI = getUserACI(state);
|
||||||
strictAssert(ourUuid, 'getOutgoingCallButtonStyle missing our uuid');
|
strictAssert(ourACI, 'getOutgoingCallButtonStyle missing our uuid');
|
||||||
|
|
||||||
if (getActiveCall(calling)) {
|
if (getActiveCall(calling)) {
|
||||||
return OutgoingCallButtonStyle.None;
|
return OutgoingCallButtonStyle.None;
|
||||||
|
@ -64,7 +64,7 @@ const getOutgoingCallButtonStyle = (
|
||||||
const call = getOwn(calling.callsByConversation, conversation.id);
|
const call = getOwn(calling.callsByConversation, conversation.id);
|
||||||
if (
|
if (
|
||||||
call?.callMode === CallMode.Group &&
|
call?.callMode === CallMode.Group &&
|
||||||
isAnybodyElseInGroupCall(call.peekInfo, ourUuid)
|
isAnybodyElseInGroupCall(call.peekInfo, ourACI)
|
||||||
) {
|
) {
|
||||||
return OutgoingCallButtonStyle.Join;
|
return OutgoingCallButtonStyle.Join;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import {
|
||||||
getTheme,
|
getTheme,
|
||||||
getUserConversationId,
|
getUserConversationId,
|
||||||
getUserNumber,
|
getUserNumber,
|
||||||
getUserUuid,
|
|
||||||
} from '../selectors/user';
|
} from '../selectors/user';
|
||||||
import { getMe } from '../selectors/conversations';
|
import { getMe } from '../selectors/conversations';
|
||||||
import { getStoriesEnabled } from '../selectors/items';
|
import { getStoriesEnabled } from '../selectors/items';
|
||||||
|
@ -28,7 +27,6 @@ const mapStateToProps = (state: StateType) => {
|
||||||
regionCode: getRegionCode(state),
|
regionCode: getRegionCode(state),
|
||||||
ourConversationId: getUserConversationId(state),
|
ourConversationId: getUserConversationId(state),
|
||||||
ourNumber: getUserNumber(state),
|
ourNumber: getUserNumber(state),
|
||||||
ourUuid: getUserUuid(state),
|
|
||||||
...me,
|
...me,
|
||||||
badge: getPreferredBadgeSelector(state)(me.badges),
|
badge: getPreferredBadgeSelector(state)(me.badges),
|
||||||
theme: getTheme(state),
|
theme: getTheme(state),
|
||||||
|
|
|
@ -11,8 +11,8 @@ import { updateRemoteConfig } from '../helpers/RemoteConfigStub';
|
||||||
const HARD_LIMIT_KEY = 'global.groupsv2.groupSizeHardLimit';
|
const HARD_LIMIT_KEY = 'global.groupsv2.groupSizeHardLimit';
|
||||||
|
|
||||||
describe('group add banned member', () => {
|
describe('group add banned member', () => {
|
||||||
const uuid = UUID.generate().toString();
|
const uuid = UUID.generate();
|
||||||
const ourUuid = UUID.generate().toString();
|
const ourUuid = UUID.generate();
|
||||||
const existing = Array.from({ length: 10 }, (_, index) => ({
|
const existing = Array.from({ length: 10 }, (_, index) => ({
|
||||||
uuid: UUID.generate().toString(),
|
uuid: UUID.generate().toString(),
|
||||||
timestamp: index,
|
timestamp: index,
|
||||||
|
@ -49,7 +49,7 @@ describe('group add banned member', () => {
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
actions.addMembersBanned?.[0]?.added?.userId ?? new Uint8Array(0)
|
actions.addMembersBanned?.[0]?.added?.userId ?? new Uint8Array(0)
|
||||||
),
|
),
|
||||||
uuid
|
uuid.toString()
|
||||||
);
|
);
|
||||||
assert.strictEqual(actions.deleteMembersBanned, null);
|
assert.strictEqual(actions.deleteMembersBanned, null);
|
||||||
});
|
});
|
||||||
|
@ -77,7 +77,7 @@ describe('group add banned member', () => {
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
actions.addMembersBanned?.[0]?.added?.userId ?? new Uint8Array(0)
|
actions.addMembersBanned?.[0]?.added?.userId ?? new Uint8Array(0)
|
||||||
),
|
),
|
||||||
uuid
|
uuid.toString()
|
||||||
);
|
);
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
deleted,
|
deleted,
|
||||||
|
@ -108,7 +108,7 @@ describe('group add banned member', () => {
|
||||||
uuid,
|
uuid,
|
||||||
ourUuid,
|
ourUuid,
|
||||||
group: {
|
group: {
|
||||||
bannedMembersV2: [{ uuid, timestamp: 1 }],
|
bannedMembersV2: [{ uuid: uuid.toString(), timestamp: 1 }],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { times } from 'lodash';
|
||||||
import { ConversationModel } from '../models/conversations';
|
import { ConversationModel } from '../models/conversations';
|
||||||
import type { ConversationAttributesType } from '../model-types.d';
|
import type { ConversationAttributesType } from '../model-types.d';
|
||||||
import { UUID } from '../types/UUID';
|
import { UUID } from '../types/UUID';
|
||||||
|
import { DAY } from '../util/durations';
|
||||||
|
|
||||||
import { routineProfileRefresh } from '../routineProfileRefresh';
|
import { routineProfileRefresh } from '../routineProfileRefresh';
|
||||||
|
|
||||||
|
@ -44,6 +45,7 @@ describe('routineProfileRefresh', () => {
|
||||||
muteExpiresAt: 0,
|
muteExpiresAt: 0,
|
||||||
profileAvatar: undefined,
|
profileAvatar: undefined,
|
||||||
profileKeyCredential: UUID.generate().toString(),
|
profileKeyCredential: UUID.generate().toString(),
|
||||||
|
profileKeyCredentialExpiration: Date.now() + 2 * DAY,
|
||||||
profileSharing: true,
|
profileSharing: true,
|
||||||
quotedMessageId: null,
|
quotedMessageId: null,
|
||||||
sealedSender: 1,
|
sealedSender: 1,
|
||||||
|
|
|
@ -157,7 +157,7 @@ describe('calling duck', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ourUuid = UUID.generate().toString();
|
const ourACI = UUID.generate().toString();
|
||||||
|
|
||||||
const getEmptyRootState = () => {
|
const getEmptyRootState = () => {
|
||||||
const rootState = rootReducer(undefined, noopAction());
|
const rootState = rootReducer(undefined, noopAction());
|
||||||
|
@ -165,7 +165,7 @@ describe('calling duck', () => {
|
||||||
...rootState,
|
...rootState,
|
||||||
user: {
|
user: {
|
||||||
...rootState.user,
|
...rootState.user,
|
||||||
ourUuid,
|
ourACI,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { reducer as rootReducer } from '../../../state/reducer';
|
import { reducer as rootReducer } from '../../../state/reducer';
|
||||||
import { noopAction } from '../../../state/ducks/noop';
|
import { noopAction } from '../../../state/ducks/noop';
|
||||||
|
import { actions as userActions } from '../../../state/ducks/user';
|
||||||
import {
|
import {
|
||||||
CallMode,
|
CallMode,
|
||||||
CallState,
|
CallState,
|
||||||
|
@ -25,7 +26,15 @@ import type {
|
||||||
import { getEmptyState } from '../../../state/ducks/calling';
|
import { getEmptyState } from '../../../state/ducks/calling';
|
||||||
|
|
||||||
describe('state/selectors/calling', () => {
|
describe('state/selectors/calling', () => {
|
||||||
const getEmptyRootState = () => rootReducer(undefined, noopAction());
|
const getEmptyRootState = () => {
|
||||||
|
const initial = rootReducer(undefined, noopAction());
|
||||||
|
return rootReducer(
|
||||||
|
initial,
|
||||||
|
userActions.userChanged({
|
||||||
|
ourACI: '00000000-0000-4000-8000-000000000000',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const getCallingState = (calling: CallingStateType) => ({
|
const getCallingState = (calling: CallingStateType) => ({
|
||||||
...getEmptyRootState(),
|
...getEmptyRootState(),
|
||||||
|
|
|
@ -2,12 +2,14 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
import * as sinon from 'sinon';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
_analyzeSenderKeyDevices,
|
_analyzeSenderKeyDevices,
|
||||||
_waitForAll,
|
_waitForAll,
|
||||||
_shouldFailSend,
|
_shouldFailSend,
|
||||||
} from '../../util/sendToGroup';
|
} from '../../util/sendToGroup';
|
||||||
|
import { UUID } from '../../types/UUID';
|
||||||
|
|
||||||
import type { DeviceType } from '../../textsecure/Types.d';
|
import type { DeviceType } from '../../textsecure/Types.d';
|
||||||
import {
|
import {
|
||||||
|
@ -23,21 +25,39 @@ import {
|
||||||
} from '../../textsecure/Errors';
|
} from '../../textsecure/Errors';
|
||||||
|
|
||||||
describe('sendToGroup', () => {
|
describe('sendToGroup', () => {
|
||||||
|
const uuidOne = UUID.generate().toString();
|
||||||
|
const uuidTwo = UUID.generate().toString();
|
||||||
|
|
||||||
|
let sandbox: sinon.SinonSandbox;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox = sinon.createSandbox();
|
||||||
|
|
||||||
|
const stub = sandbox.stub(UUID, 'lookup');
|
||||||
|
stub.withArgs(uuidOne).returns(new UUID(uuidOne));
|
||||||
|
stub.withArgs(uuidTwo).returns(new UUID(uuidTwo));
|
||||||
|
stub.returns(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
describe('#_analyzeSenderKeyDevices', () => {
|
describe('#_analyzeSenderKeyDevices', () => {
|
||||||
function getDefaultDeviceList(): Array<DeviceType> {
|
function getDefaultDeviceList(): Array<DeviceType> {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
identifier: 'ident-guid-one',
|
identifier: uuidOne,
|
||||||
id: 1,
|
id: 1,
|
||||||
registrationId: 11,
|
registrationId: 11,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: 'ident-guid-one',
|
identifier: uuidOne,
|
||||||
id: 2,
|
id: 2,
|
||||||
registrationId: 22,
|
registrationId: 22,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: 'ident-guid-two',
|
identifier: uuidTwo,
|
||||||
id: 2,
|
id: 2,
|
||||||
registrationId: 33,
|
registrationId: 33,
|
||||||
},
|
},
|
||||||
|
@ -76,17 +96,17 @@ describe('sendToGroup', () => {
|
||||||
|
|
||||||
assert.deepEqual(newToMemberDevices, [
|
assert.deepEqual(newToMemberDevices, [
|
||||||
{
|
{
|
||||||
identifier: 'ident-guid-one',
|
identifier: uuidOne,
|
||||||
id: 2,
|
id: 2,
|
||||||
registrationId: 22,
|
registrationId: 22,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: 'ident-guid-two',
|
identifier: uuidTwo,
|
||||||
id: 2,
|
id: 2,
|
||||||
registrationId: 33,
|
registrationId: 33,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
assert.deepEqual(newToMemberUuids, ['ident-guid-one', 'ident-guid-two']);
|
assert.deepEqual(newToMemberUuids, [uuidOne, uuidTwo]);
|
||||||
assert.isEmpty(removedFromMemberDevices);
|
assert.isEmpty(removedFromMemberDevices);
|
||||||
assert.isEmpty(removedFromMemberUuids);
|
assert.isEmpty(removedFromMemberUuids);
|
||||||
});
|
});
|
||||||
|
@ -108,20 +128,17 @@ describe('sendToGroup', () => {
|
||||||
assert.isEmpty(newToMemberUuids);
|
assert.isEmpty(newToMemberUuids);
|
||||||
assert.deepEqual(removedFromMemberDevices, [
|
assert.deepEqual(removedFromMemberDevices, [
|
||||||
{
|
{
|
||||||
identifier: 'ident-guid-one',
|
identifier: uuidOne,
|
||||||
id: 2,
|
id: 2,
|
||||||
registrationId: 22,
|
registrationId: 22,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: 'ident-guid-two',
|
identifier: uuidTwo,
|
||||||
id: 2,
|
id: 2,
|
||||||
registrationId: 33,
|
registrationId: 33,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
assert.deepEqual(removedFromMemberUuids, [
|
assert.deepEqual(removedFromMemberUuids, [uuidOne, uuidTwo]);
|
||||||
'ident-guid-one',
|
|
||||||
'ident-guid-two',
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
it('returns empty removals if partial send', () => {
|
it('returns empty removals if partial send', () => {
|
||||||
const memberDevices = getDefaultDeviceList();
|
const memberDevices = getDefaultDeviceList();
|
||||||
|
|
|
@ -2,16 +2,11 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
/* eslint-disable no-await-in-loop, no-console */
|
/* eslint-disable no-await-in-loop, no-console */
|
||||||
|
|
||||||
|
import assert from 'assert';
|
||||||
import type { PrimaryDevice } from '@signalapp/mock-server';
|
import type { PrimaryDevice } from '@signalapp/mock-server';
|
||||||
|
|
||||||
import {
|
import type { App } from './fixtures';
|
||||||
Bootstrap,
|
import { Bootstrap, debug, stats, RUN_COUNT, DISCARD_COUNT } from './fixtures';
|
||||||
debug,
|
|
||||||
saveLogs,
|
|
||||||
stats,
|
|
||||||
RUN_COUNT,
|
|
||||||
DISCARD_COUNT,
|
|
||||||
} from './fixtures';
|
|
||||||
|
|
||||||
const CONVERSATION_SIZE = 1000; // messages
|
const CONVERSATION_SIZE = 1000; // messages
|
||||||
const DELAY = 50; // milliseconds
|
const DELAY = 50; // milliseconds
|
||||||
|
@ -22,9 +17,10 @@ const DELAY = 50; // milliseconds
|
||||||
});
|
});
|
||||||
|
|
||||||
await bootstrap.init();
|
await bootstrap.init();
|
||||||
const app = await bootstrap.link();
|
|
||||||
|
|
||||||
|
let app: App | undefined;
|
||||||
try {
|
try {
|
||||||
|
app = await bootstrap.link();
|
||||||
const { server, contacts, phone, desktop } = bootstrap;
|
const { server, contacts, phone, desktop } = bootstrap;
|
||||||
|
|
||||||
const [first, second] = contacts;
|
const [first, second] = contacts;
|
||||||
|
@ -65,6 +61,7 @@ const DELAY = 50; // milliseconds
|
||||||
};
|
};
|
||||||
|
|
||||||
const measure = async (): Promise<void> => {
|
const measure = async (): Promise<void> => {
|
||||||
|
assert(app);
|
||||||
const window = await app.getWindow();
|
const window = await app.getWindow();
|
||||||
|
|
||||||
const leftPane = window.locator('.left-pane-wrapper');
|
const leftPane = window.locator('.left-pane-wrapper');
|
||||||
|
@ -102,10 +99,10 @@ const DELAY = 50; // milliseconds
|
||||||
|
|
||||||
await Promise.all([sendQueue(), measure()]);
|
await Promise.all([sendQueue(), measure()]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await saveLogs(bootstrap);
|
await bootstrap.saveLogs();
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
await app.close();
|
await app?.close();
|
||||||
await bootstrap.teardown();
|
await bootstrap.teardown();
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
import createDebug from 'debug';
|
import createDebug from 'debug';
|
||||||
import fs from 'fs/promises';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
import { Bootstrap } from '../bootstrap';
|
import { Bootstrap } from '../bootstrap';
|
||||||
|
|
||||||
|
@ -63,19 +61,6 @@ export function stats(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveLogs(bootstrap: Bootstrap): Promise<void> {
|
|
||||||
const { ARTIFACTS_DIR } = process.env;
|
|
||||||
if (!ARTIFACTS_DIR) {
|
|
||||||
console.error('Not saving logs. Please set ARTIFACTS_DIR env variable');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await fs.mkdir(ARTIFACTS_DIR, { recursive: true });
|
|
||||||
|
|
||||||
const { logsDir } = bootstrap;
|
|
||||||
await fs.rename(logsDir, path.join(ARTIFACTS_DIR, 'logs'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can happen if electron exits prematurely
|
// Can happen if electron exits prematurely
|
||||||
process.on('unhandledRejection', reason => {
|
process.on('unhandledRejection', reason => {
|
||||||
console.error('Unhandled rejection:');
|
console.error('Unhandled rejection:');
|
||||||
|
|
|
@ -9,10 +9,11 @@ import {
|
||||||
EnvelopeType,
|
EnvelopeType,
|
||||||
ReceiptType,
|
ReceiptType,
|
||||||
} from '@signalapp/mock-server';
|
} from '@signalapp/mock-server';
|
||||||
|
|
||||||
|
import type { App } from './fixtures';
|
||||||
import {
|
import {
|
||||||
Bootstrap,
|
Bootstrap,
|
||||||
debug,
|
debug,
|
||||||
saveLogs,
|
|
||||||
stats,
|
stats,
|
||||||
RUN_COUNT,
|
RUN_COUNT,
|
||||||
GROUP_SIZE,
|
GROUP_SIZE,
|
||||||
|
@ -44,9 +45,11 @@ const LAST_MESSAGE = 'start sending messages now';
|
||||||
.pinGroup(group)
|
.pinGroup(group)
|
||||||
);
|
);
|
||||||
|
|
||||||
const app = await bootstrap.link();
|
let app: App | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
app = await bootstrap.link();
|
||||||
|
|
||||||
const { server, desktop } = bootstrap;
|
const { server, desktop } = bootstrap;
|
||||||
const [first] = members;
|
const [first] = members;
|
||||||
|
|
||||||
|
@ -179,10 +182,10 @@ const LAST_MESSAGE = 'start sending messages now';
|
||||||
|
|
||||||
console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) });
|
console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await saveLogs(bootstrap);
|
await bootstrap.saveLogs();
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
await app.close();
|
await app?.close();
|
||||||
await bootstrap.teardown();
|
await bootstrap.teardown();
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -6,14 +6,8 @@ import assert from 'assert';
|
||||||
|
|
||||||
import { ReceiptType } from '@signalapp/mock-server';
|
import { ReceiptType } from '@signalapp/mock-server';
|
||||||
|
|
||||||
import {
|
import type { App } from './fixtures';
|
||||||
Bootstrap,
|
import { Bootstrap, debug, stats, RUN_COUNT, DISCARD_COUNT } from './fixtures';
|
||||||
debug,
|
|
||||||
saveLogs,
|
|
||||||
stats,
|
|
||||||
RUN_COUNT,
|
|
||||||
DISCARD_COUNT,
|
|
||||||
} from './fixtures';
|
|
||||||
|
|
||||||
const CONVERSATION_SIZE = 500; // messages
|
const CONVERSATION_SIZE = 500; // messages
|
||||||
|
|
||||||
|
@ -25,9 +19,11 @@ const LAST_MESSAGE = 'start sending messages now';
|
||||||
});
|
});
|
||||||
|
|
||||||
await bootstrap.init();
|
await bootstrap.init();
|
||||||
const app = await bootstrap.link();
|
let app: App | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
app = await bootstrap.link();
|
||||||
|
|
||||||
const { server, contacts, phone, desktop } = bootstrap;
|
const { server, contacts, phone, desktop } = bootstrap;
|
||||||
|
|
||||||
const [first] = contacts;
|
const [first] = contacts;
|
||||||
|
@ -136,10 +132,10 @@ const LAST_MESSAGE = 'start sending messages now';
|
||||||
|
|
||||||
console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) });
|
console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await saveLogs(bootstrap);
|
await bootstrap.saveLogs();
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
await app.close();
|
await app?.close();
|
||||||
await bootstrap.teardown();
|
await bootstrap.teardown();
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
import { ReceiptType } from '@signalapp/mock-server';
|
import { ReceiptType } from '@signalapp/mock-server';
|
||||||
|
|
||||||
import { debug, Bootstrap, saveLogs, stats, RUN_COUNT } from './fixtures';
|
import { debug, Bootstrap, stats, RUN_COUNT } from './fixtures';
|
||||||
|
|
||||||
const MESSAGE_BATCH_SIZE = 1000; // messages
|
const MESSAGE_BATCH_SIZE = 1000; // messages
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ const ENABLE_RECEIPTS = Boolean(process.env.ENABLE_RECEIPTS);
|
||||||
console.log('stats info=%j', { messagesPerSec: stats(messagesPerSec) });
|
console.log('stats info=%j', { messagesPerSec: stats(messagesPerSec) });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await saveLogs(bootstrap);
|
await bootstrap.saveLogs();
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
await bootstrap.teardown();
|
await bootstrap.teardown();
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
import { StorageState } from '@signalapp/mock-server';
|
import { StorageState } from '@signalapp/mock-server';
|
||||||
|
|
||||||
import { Bootstrap, saveLogs } from './fixtures';
|
import type { App } from './fixtures';
|
||||||
|
import { Bootstrap } from './fixtures';
|
||||||
|
|
||||||
const CONTACT_COUNT = 1000;
|
const CONTACT_COUNT = 1000;
|
||||||
|
|
||||||
|
@ -43,8 +44,9 @@ const CONTACT_COUNT = 1000;
|
||||||
await phone.setStorageState(state);
|
await phone.setStorageState(state);
|
||||||
|
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const app = await bootstrap.link();
|
let app: App | undefined;
|
||||||
try {
|
try {
|
||||||
|
app = await bootstrap.link();
|
||||||
const window = await app.getWindow();
|
const window = await app.getWindow();
|
||||||
|
|
||||||
const leftPane = window.locator('.left-pane-wrapper');
|
const leftPane = window.locator('.left-pane-wrapper');
|
||||||
|
@ -58,10 +60,10 @@ const CONTACT_COUNT = 1000;
|
||||||
const duration = Date.now() - start;
|
const duration = Date.now() - start;
|
||||||
console.log(`Took: ${(duration / 1000).toFixed(2)} seconds`);
|
console.log(`Took: ${(duration / 1000).toFixed(2)} seconds`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await saveLogs(bootstrap);
|
await bootstrap.saveLogs();
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
await app.close();
|
await app?.close();
|
||||||
await bootstrap.teardown();
|
await bootstrap.teardown();
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -8,7 +8,7 @@ import os from 'os';
|
||||||
import createDebug from 'debug';
|
import createDebug from 'debug';
|
||||||
|
|
||||||
import type { Device, PrimaryDevice } from '@signalapp/mock-server';
|
import type { Device, PrimaryDevice } from '@signalapp/mock-server';
|
||||||
import { Server, loadCertificates } from '@signalapp/mock-server';
|
import { Server, UUIDKind, loadCertificates } from '@signalapp/mock-server';
|
||||||
import { MAX_READ_KEYS as MAX_STORAGE_READ_KEYS } from '../services/storageConstants';
|
import { MAX_READ_KEYS as MAX_STORAGE_READ_KEYS } from '../services/storageConstants';
|
||||||
import * as durations from '../util/durations';
|
import * as durations from '../util/durations';
|
||||||
import { App } from './playwright';
|
import { App } from './playwright';
|
||||||
|
@ -156,7 +156,7 @@ export class Bootstrap {
|
||||||
);
|
);
|
||||||
|
|
||||||
this.privPhone = await this.server.createPrimaryDevice({
|
this.privPhone = await this.server.createPrimaryDevice({
|
||||||
profileName: 'Mock',
|
profileName: 'Myself',
|
||||||
contacts: this.contacts,
|
contacts: this.contacts,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -206,10 +206,12 @@ export class Bootstrap {
|
||||||
await this.phone.addSingleUseKey(this.desktop, desktopKey);
|
await this.phone.addSingleUseKey(this.desktop, desktopKey);
|
||||||
|
|
||||||
for (const contact of this.contacts) {
|
for (const contact of this.contacts) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
for (const uuidKind of [UUIDKind.ACI, UUIDKind.PNI]) {
|
||||||
const contactKey = await this.desktop.popSingleUseKey();
|
// eslint-disable-next-line no-await-in-loop
|
||||||
// eslint-disable-next-line no-await-in-loop
|
const contactKey = await this.desktop.popSingleUseKey(uuidKind);
|
||||||
await contact.addSingleUseKey(this.desktop, contactKey);
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await contact.addSingleUseKey(this.desktop, contactKey, uuidKind);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.phone.waitForSync(this.desktop);
|
await this.phone.waitForSync(this.desktop);
|
||||||
|
@ -254,6 +256,25 @@ export class Bootstrap {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async saveLogs(): Promise<void> {
|
||||||
|
const { ARTIFACTS_DIR } = process.env;
|
||||||
|
if (!ARTIFACTS_DIR) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('Not saving logs. Please set ARTIFACTS_DIR env variable');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.mkdir(ARTIFACTS_DIR, { recursive: true });
|
||||||
|
|
||||||
|
const outDir = await fs.mkdtemp(path.join(ARTIFACTS_DIR, 'logs-'));
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(`Saving logs to ${outDir}`);
|
||||||
|
|
||||||
|
const { logsDir } = this;
|
||||||
|
await fs.rename(logsDir, path.join(outDir));
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Getters
|
// Getters
|
||||||
//
|
//
|
||||||
|
@ -299,13 +320,19 @@ export class Bootstrap {
|
||||||
storageProfile: 'mock',
|
storageProfile: 'mock',
|
||||||
serverUrl: url,
|
serverUrl: url,
|
||||||
storageUrl: url,
|
storageUrl: url,
|
||||||
directoryUrl: url,
|
|
||||||
cdn: {
|
cdn: {
|
||||||
'0': url,
|
'0': url,
|
||||||
'2': url,
|
'2': url,
|
||||||
},
|
},
|
||||||
updatesEnabled: false,
|
updatesEnabled: false,
|
||||||
|
|
||||||
|
directoryVersion: 3,
|
||||||
|
directoryV3Url: url,
|
||||||
|
directoryV3MRENCLAVE:
|
||||||
|
'51133fecb3fa18aaf0c8f64cb763656d3272d9faaacdb26ae7df082e414fb142',
|
||||||
|
directoryV3Root:
|
||||||
|
'-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n',
|
||||||
|
|
||||||
...this.options.extraConfig,
|
...this.options.extraConfig,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
240
ts/test-mock/gv2/accept_invite_test.ts
Normal file
240
ts/test-mock/gv2/accept_invite_test.ts
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
import type { Group } from '@signalapp/mock-server';
|
||||||
|
import { UUIDKind } from '@signalapp/mock-server';
|
||||||
|
import createDebug from 'debug';
|
||||||
|
|
||||||
|
import * as durations from '../../util/durations';
|
||||||
|
import { Bootstrap } from '../bootstrap';
|
||||||
|
import type { App } from '../bootstrap';
|
||||||
|
|
||||||
|
export const debug = createDebug('mock:test:gv2');
|
||||||
|
|
||||||
|
describe('gv2', function needsName() {
|
||||||
|
this.timeout(durations.MINUTE);
|
||||||
|
|
||||||
|
let bootstrap: Bootstrap;
|
||||||
|
let app: App;
|
||||||
|
let group: Group;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
bootstrap = new Bootstrap();
|
||||||
|
await bootstrap.init();
|
||||||
|
|
||||||
|
const { contacts } = bootstrap;
|
||||||
|
|
||||||
|
const [first, second] = contacts;
|
||||||
|
|
||||||
|
group = await first.createGroup({
|
||||||
|
title: 'Invite by PNI',
|
||||||
|
members: [first, second],
|
||||||
|
});
|
||||||
|
|
||||||
|
app = await bootstrap.link();
|
||||||
|
|
||||||
|
const { desktop } = bootstrap;
|
||||||
|
|
||||||
|
group = await first.inviteToGroup(group, desktop, {
|
||||||
|
uuidKind: UUIDKind.PNI,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify that created group has pending member
|
||||||
|
assert.strictEqual(group.state?.members?.length, 2);
|
||||||
|
assert(!group.getMemberByUUID(desktop.uuid));
|
||||||
|
assert(!group.getMemberByUUID(desktop.pni));
|
||||||
|
assert(!group.getPendingMemberByUUID(desktop.uuid));
|
||||||
|
assert(group.getPendingMemberByUUID(desktop.pni));
|
||||||
|
|
||||||
|
const window = await app.getWindow();
|
||||||
|
|
||||||
|
const leftPane = window.locator('.left-pane-wrapper');
|
||||||
|
|
||||||
|
debug('Opening group');
|
||||||
|
await leftPane
|
||||||
|
.locator(
|
||||||
|
'_react=ConversationListItem' +
|
||||||
|
`[title = ${JSON.stringify(group.title)}]`
|
||||||
|
)
|
||||||
|
.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async function after() {
|
||||||
|
if (this.currentTest?.state !== 'passed') {
|
||||||
|
await bootstrap.saveLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
await app.close();
|
||||||
|
await bootstrap.teardown();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept PNI invite and modify the group state', async () => {
|
||||||
|
const { phone, contacts, desktop } = bootstrap;
|
||||||
|
const [first, second] = contacts;
|
||||||
|
|
||||||
|
const window = await app.getWindow();
|
||||||
|
|
||||||
|
const conversationStack = window.locator('.conversation-stack');
|
||||||
|
|
||||||
|
debug('Accepting');
|
||||||
|
await conversationStack
|
||||||
|
.locator('.module-message-request-actions button >> "Accept"')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
group = await phone.waitForGroupUpdate(group);
|
||||||
|
assert.strictEqual(group.revision, 2);
|
||||||
|
assert.strictEqual(group.state?.members?.length, 3);
|
||||||
|
assert(group.getMemberByUUID(desktop.uuid));
|
||||||
|
assert(!group.getMemberByUUID(desktop.pni));
|
||||||
|
assert(!group.getPendingMemberByUUID(desktop.uuid));
|
||||||
|
assert(!group.getPendingMemberByUUID(desktop.pni));
|
||||||
|
|
||||||
|
debug('Checking that notifications are present');
|
||||||
|
await window
|
||||||
|
.locator(`"${first.profileName} invited you to the group."`)
|
||||||
|
.waitFor();
|
||||||
|
await window
|
||||||
|
.locator(
|
||||||
|
`"You accepted an invitation to the group from ${first.profileName}."`
|
||||||
|
)
|
||||||
|
.waitFor();
|
||||||
|
|
||||||
|
debug('Invite PNI again');
|
||||||
|
group = await second.inviteToGroup(group, desktop, {
|
||||||
|
uuidKind: UUIDKind.PNI,
|
||||||
|
});
|
||||||
|
assert(group.getMemberByUUID(desktop.uuid));
|
||||||
|
assert(group.getPendingMemberByUUID(desktop.pni));
|
||||||
|
|
||||||
|
await window
|
||||||
|
.locator(`"${second.profileName} invited you to the group."`)
|
||||||
|
.waitFor();
|
||||||
|
|
||||||
|
debug('Verify that message request state is not visible');
|
||||||
|
await conversationStack
|
||||||
|
.locator('.module-message-request-actions button >> "Accept"')
|
||||||
|
.waitFor({ state: 'hidden' });
|
||||||
|
|
||||||
|
debug('Leave the group through settings');
|
||||||
|
|
||||||
|
await conversationStack
|
||||||
|
.locator('button.module-ConversationHeader__button--more')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await conversationStack
|
||||||
|
.locator('.react-contextmenu-item >> "Group settings"')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await conversationStack
|
||||||
|
.locator('.conversation-details-panel >> "Leave group"')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await window.locator('.module-Modal button >> "Leave"').click();
|
||||||
|
|
||||||
|
debug('Waiting for final group update');
|
||||||
|
group = await phone.waitForGroupUpdate(group);
|
||||||
|
assert.strictEqual(group.revision, 4);
|
||||||
|
assert.strictEqual(group.state?.members?.length, 2);
|
||||||
|
assert(!group.getMemberByUUID(desktop.uuid));
|
||||||
|
assert(!group.getMemberByUUID(desktop.pni));
|
||||||
|
assert(!group.getPendingMemberByUUID(desktop.uuid));
|
||||||
|
assert(group.getPendingMemberByUUID(desktop.pni));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should decline PNI invite and modify the group state', async () => {
|
||||||
|
const { phone, desktop } = bootstrap;
|
||||||
|
|
||||||
|
const window = await app.getWindow();
|
||||||
|
|
||||||
|
const conversationStack = window.locator('.conversation-stack');
|
||||||
|
|
||||||
|
debug('Declining');
|
||||||
|
await conversationStack
|
||||||
|
.locator('.module-message-request-actions button >> "Delete"')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
debug('waiting for confirmation modal');
|
||||||
|
await window.locator('.module-Modal button >> "Delete and Leave"').click();
|
||||||
|
|
||||||
|
group = await phone.waitForGroupUpdate(group);
|
||||||
|
assert.strictEqual(group.revision, 2);
|
||||||
|
assert.strictEqual(group.state?.members?.length, 2);
|
||||||
|
assert(!group.getMemberByUUID(desktop.uuid));
|
||||||
|
assert(!group.getMemberByUUID(desktop.pni));
|
||||||
|
assert(!group.getPendingMemberByUUID(desktop.uuid));
|
||||||
|
assert(!group.getPendingMemberByUUID(desktop.pni));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept ACI invite with extra PNI on the invite list', async () => {
|
||||||
|
const { phone, contacts, desktop } = bootstrap;
|
||||||
|
const [first, second] = contacts;
|
||||||
|
|
||||||
|
const window = await app.getWindow();
|
||||||
|
|
||||||
|
debug('Sending another invite');
|
||||||
|
|
||||||
|
// Invite ACI from another contact
|
||||||
|
group = await second.inviteToGroup(group, desktop, {
|
||||||
|
uuidKind: UUIDKind.ACI,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conversationStack = window.locator('.conversation-stack');
|
||||||
|
|
||||||
|
debug('Accepting');
|
||||||
|
await conversationStack
|
||||||
|
.locator('.module-message-request-actions button >> "Accept"')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
debug('Verifying notifications');
|
||||||
|
await window
|
||||||
|
.locator(`"${first.profileName} invited you to the group."`)
|
||||||
|
.waitFor();
|
||||||
|
await window.locator('"You were invited to the group."').waitFor();
|
||||||
|
await window
|
||||||
|
.locator(
|
||||||
|
`"You accepted an invitation to the group from ${second.profileName}."`
|
||||||
|
)
|
||||||
|
.waitFor();
|
||||||
|
|
||||||
|
group = await phone.waitForGroupUpdate(group);
|
||||||
|
assert.strictEqual(group.revision, 3);
|
||||||
|
assert.strictEqual(group.state?.members?.length, 3);
|
||||||
|
assert(group.getMemberByUUID(desktop.uuid));
|
||||||
|
assert(!group.getMemberByUUID(desktop.pni));
|
||||||
|
assert(!group.getPendingMemberByUUID(desktop.uuid));
|
||||||
|
assert(group.getPendingMemberByUUID(desktop.pni));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should decline ACI invite with extra PNI on the invite list', async () => {
|
||||||
|
const { phone, contacts, desktop } = bootstrap;
|
||||||
|
const [, second] = contacts;
|
||||||
|
|
||||||
|
const window = await app.getWindow();
|
||||||
|
|
||||||
|
debug('Sending another invite');
|
||||||
|
|
||||||
|
// Invite ACI from another contact
|
||||||
|
group = await second.inviteToGroup(group, desktop, {
|
||||||
|
uuidKind: UUIDKind.ACI,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conversationStack = window.locator('.conversation-stack');
|
||||||
|
|
||||||
|
debug('Declining');
|
||||||
|
await conversationStack
|
||||||
|
.locator('.module-message-request-actions button >> "Delete"')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
debug('waiting for confirmation modal');
|
||||||
|
await window.locator('.module-Modal button >> "Delete and Leave"').click();
|
||||||
|
|
||||||
|
group = await phone.waitForGroupUpdate(group);
|
||||||
|
assert.strictEqual(group.revision, 3);
|
||||||
|
assert.strictEqual(group.state?.members?.length, 2);
|
||||||
|
assert(!group.getMemberByUUID(desktop.uuid));
|
||||||
|
assert(!group.getMemberByUUID(desktop.pni));
|
||||||
|
assert(!group.getPendingMemberByUUID(desktop.uuid));
|
||||||
|
assert(group.getPendingMemberByUUID(desktop.pni));
|
||||||
|
});
|
||||||
|
});
|
|
@ -63,7 +63,11 @@ describe('gv2', function needsName() {
|
||||||
app = await bootstrap.link();
|
app = await bootstrap.link();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async function after() {
|
||||||
|
if (this.currentTest?.state !== 'passed') {
|
||||||
|
await bootstrap.saveLogs();
|
||||||
|
}
|
||||||
|
|
||||||
await app.close();
|
await app.close();
|
||||||
await bootstrap.teardown();
|
await bootstrap.teardown();
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,15 @@ describe('storage service', function needsName() {
|
||||||
({ bootstrap, app } = await initStorage());
|
({ bootstrap, app } = await initStorage());
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async function after() {
|
||||||
|
if (!bootstrap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentTest?.state !== 'passed') {
|
||||||
|
await bootstrap.saveLogs();
|
||||||
|
}
|
||||||
|
|
||||||
await app.close();
|
await app.close();
|
||||||
await bootstrap.teardown();
|
await bootstrap.teardown();
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,7 +20,15 @@ describe('storage service', function needsName() {
|
||||||
({ bootstrap, app } = await initStorage());
|
({ bootstrap, app } = await initStorage());
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async function after() {
|
||||||
|
if (!bootstrap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentTest?.state !== 'passed') {
|
||||||
|
await bootstrap.saveLogs();
|
||||||
|
}
|
||||||
|
|
||||||
await app.close();
|
await app.close();
|
||||||
await bootstrap.teardown();
|
await bootstrap.teardown();
|
||||||
});
|
});
|
||||||
|
|
|
@ -39,63 +39,68 @@ export async function initStorage(
|
||||||
|
|
||||||
await bootstrap.init();
|
await bootstrap.init();
|
||||||
|
|
||||||
// Populate storage service
|
try {
|
||||||
const { contacts, phone } = bootstrap;
|
// Populate storage service
|
||||||
|
const { contacts, phone } = bootstrap;
|
||||||
|
|
||||||
const [firstContact] = contacts;
|
const [firstContact] = contacts;
|
||||||
|
|
||||||
const members = [...contacts].slice(0, GROUP_SIZE);
|
const members = [...contacts].slice(0, GROUP_SIZE);
|
||||||
|
|
||||||
const group = await phone.createGroup({
|
const group = await phone.createGroup({
|
||||||
title: 'Mock Group',
|
title: 'Mock Group',
|
||||||
members: [phone, ...members],
|
members: [phone, ...members],
|
||||||
});
|
|
||||||
|
|
||||||
let state = StorageState.getEmpty();
|
|
||||||
|
|
||||||
state = state.updateAccount({
|
|
||||||
profileKey: phone.profileKey.serialize(),
|
|
||||||
e164: phone.device.number,
|
|
||||||
});
|
|
||||||
|
|
||||||
state = state
|
|
||||||
.addGroup(group, {
|
|
||||||
whitelisted: true,
|
|
||||||
})
|
|
||||||
.pinGroup(group);
|
|
||||||
|
|
||||||
for (const contact of contacts) {
|
|
||||||
state = state.addContact(contact, {
|
|
||||||
identityState: Proto.ContactRecord.IdentityState.VERIFIED,
|
|
||||||
whitelisted: true,
|
|
||||||
|
|
||||||
identityKey: contact.publicKey.serialize(),
|
|
||||||
profileKey: contact.profileKey.serialize(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let state = StorageState.getEmpty();
|
||||||
|
|
||||||
|
state = state.updateAccount({
|
||||||
|
profileKey: phone.profileKey.serialize(),
|
||||||
|
e164: phone.device.number,
|
||||||
|
});
|
||||||
|
|
||||||
|
state = state
|
||||||
|
.addGroup(group, {
|
||||||
|
whitelisted: true,
|
||||||
|
})
|
||||||
|
.pinGroup(group);
|
||||||
|
|
||||||
|
for (const contact of contacts) {
|
||||||
|
state = state.addContact(contact, {
|
||||||
|
identityState: Proto.ContactRecord.IdentityState.VERIFIED,
|
||||||
|
whitelisted: true,
|
||||||
|
|
||||||
|
identityKey: contact.publicKey.serialize(),
|
||||||
|
profileKey: contact.profileKey.serialize(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
state = state.pin(firstContact);
|
||||||
|
|
||||||
|
await phone.setStorageState(state);
|
||||||
|
|
||||||
|
// Link new device
|
||||||
|
const app = await bootstrap.link();
|
||||||
|
|
||||||
|
const { desktop } = bootstrap;
|
||||||
|
|
||||||
|
// Send a message to the group and the first contact
|
||||||
|
const contactSend = contacts[0].sendText(desktop, 'hello from contact', {
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
sealed: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupSend = members[0].sendText(desktop, 'hello in group', {
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
sealed: true,
|
||||||
|
group,
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([contactSend, groupSend]);
|
||||||
|
|
||||||
|
return { bootstrap, app, group, members };
|
||||||
|
} catch (error) {
|
||||||
|
await bootstrap.saveLogs();
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
state = state.pin(firstContact);
|
|
||||||
|
|
||||||
await phone.setStorageState(state);
|
|
||||||
|
|
||||||
// Link new device
|
|
||||||
const app = await bootstrap.link();
|
|
||||||
|
|
||||||
const { desktop } = bootstrap;
|
|
||||||
|
|
||||||
// Send a message to the group and the first contact
|
|
||||||
const contactSend = contacts[0].sendText(desktop, 'hello from contact', {
|
|
||||||
timestamp: bootstrap.getTimestamp(),
|
|
||||||
sealed: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const groupSend = members[0].sendText(desktop, 'hello in group', {
|
|
||||||
timestamp: bootstrap.getTimestamp(),
|
|
||||||
sealed: true,
|
|
||||||
group,
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all([contactSend, groupSend]);
|
|
||||||
|
|
||||||
return { bootstrap, app, group, members };
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,15 @@ describe('storage service', function needsName() {
|
||||||
({ bootstrap, app } = await initStorage());
|
({ bootstrap, app } = await initStorage());
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async function after() {
|
||||||
|
if (!bootstrap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentTest?.state !== 'passed') {
|
||||||
|
await bootstrap.saveLogs();
|
||||||
|
}
|
||||||
|
|
||||||
await app.close();
|
await app.close();
|
||||||
await bootstrap.teardown();
|
await bootstrap.teardown();
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,15 @@ describe('storage service', function needsName() {
|
||||||
({ bootstrap, app } = await initStorage());
|
({ bootstrap, app } = await initStorage());
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async function after() {
|
||||||
|
if (!bootstrap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentTest?.state !== 'passed') {
|
||||||
|
await bootstrap.saveLogs();
|
||||||
|
}
|
||||||
|
|
||||||
await app.close();
|
await app.close();
|
||||||
await bootstrap.teardown();
|
await bootstrap.teardown();
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,15 @@ describe('storage service', function needsName() {
|
||||||
({ bootstrap, app, group } = await initStorage());
|
({ bootstrap, app, group } = await initStorage());
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async function after() {
|
||||||
|
if (!bootstrap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentTest?.state !== 'passed') {
|
||||||
|
await bootstrap.saveLogs();
|
||||||
|
}
|
||||||
|
|
||||||
await app.close();
|
await app.close();
|
||||||
await bootstrap.teardown();
|
await bootstrap.teardown();
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@ import PQueue from 'p-queue';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
|
|
||||||
import EventTarget from './EventTarget';
|
import EventTarget from './EventTarget';
|
||||||
import type { WebAPIType, GroupCredentialType } from './WebAPI';
|
import type { WebAPIType } from './WebAPI';
|
||||||
import { HTTPError } from './Errors';
|
import { HTTPError } from './Errors';
|
||||||
import type { KeyPairType } from './Types.d';
|
import type { KeyPairType } from './Types.d';
|
||||||
import ProvisioningCipher from './ProvisioningCipher';
|
import ProvisioningCipher from './ProvisioningCipher';
|
||||||
|
@ -755,14 +755,6 @@ export default class AccountManager extends EventTarget {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupCredentials(
|
|
||||||
startDay: number,
|
|
||||||
endDay: number,
|
|
||||||
uuidKind: UUIDKind
|
|
||||||
): Promise<Array<GroupCredentialType>> {
|
|
||||||
return this.server.getGroupCredentials(startDay, endDay, uuidKind);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Takes the same object returned by generateKeys
|
// Takes the same object returned by generateKeys
|
||||||
async confirmKeys(
|
async confirmKeys(
|
||||||
keys: GeneratedKeysType,
|
keys: GeneratedKeysType,
|
||||||
|
|
|
@ -759,7 +759,9 @@ export default class MessageReceiver
|
||||||
// Proto.Envelope fields
|
// Proto.Envelope fields
|
||||||
type: decoded.type,
|
type: decoded.type,
|
||||||
source: decoded.source || item.source,
|
source: decoded.source || item.source,
|
||||||
sourceUuid: decoded.sourceUuid || item.sourceUuid,
|
sourceUuid: decoded.sourceUuid
|
||||||
|
? UUID.cast(decoded.sourceUuid)
|
||||||
|
: item.sourceUuid,
|
||||||
sourceDevice: decoded.sourceDevice || item.sourceDevice,
|
sourceDevice: decoded.sourceDevice || item.sourceDevice,
|
||||||
destinationUuid: new UUID(
|
destinationUuid: new UUID(
|
||||||
decoded.destinationUuid || item.destinationUuid || ourUuid.toString()
|
decoded.destinationUuid || item.destinationUuid || ourUuid.toString()
|
||||||
|
|
4
ts/textsecure/Types.d.ts
vendored
4
ts/textsecure/Types.d.ts
vendored
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import type { SignalService as Proto } from '../protobuf';
|
import type { SignalService as Proto } from '../protobuf';
|
||||||
import type { IncomingWebSocketRequest } from './WebsocketResources';
|
import type { IncomingWebSocketRequest } from './WebsocketResources';
|
||||||
import type { UUID } from '../types/UUID';
|
import type { UUID, UUIDStringType } from '../types/UUID';
|
||||||
import type { TextAttachmentType } from '../types/Attachment';
|
import type { TextAttachmentType } from '../types/Attachment';
|
||||||
import { GiftBadgeStates } from '../components/conversation/Message';
|
import { GiftBadgeStates } from '../components/conversation/Message';
|
||||||
import { MIMEType } from '../types/MIME';
|
import { MIMEType } from '../types/MIME';
|
||||||
|
@ -84,7 +84,7 @@ export type ProcessedEnvelope = Readonly<{
|
||||||
// Mostly from Proto.Envelope except for null/undefined
|
// Mostly from Proto.Envelope except for null/undefined
|
||||||
type: Proto.Envelope.Type;
|
type: Proto.Envelope.Type;
|
||||||
source?: string;
|
source?: string;
|
||||||
sourceUuid?: string;
|
sourceUuid?: UUIDStringType;
|
||||||
sourceDevice?: number;
|
sourceDevice?: number;
|
||||||
destinationUuid: UUID;
|
destinationUuid: UUID;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
|
|
@ -490,7 +490,7 @@ const URL_CALLS = {
|
||||||
directoryAuthV2: 'v2/directory/auth',
|
directoryAuthV2: 'v2/directory/auth',
|
||||||
discovery: 'v1/discovery',
|
discovery: 'v1/discovery',
|
||||||
getGroupAvatarUpload: 'v1/groups/avatar/form',
|
getGroupAvatarUpload: 'v1/groups/avatar/form',
|
||||||
getGroupCredentials: 'v1/certificate/group',
|
getGroupCredentials: 'v1/certificate/auth/group',
|
||||||
getIceServers: 'v1/accounts/turn',
|
getIceServers: 'v1/accounts/turn',
|
||||||
getStickerPackUpload: 'v1/sticker/pack/form',
|
getStickerPackUpload: 'v1/sticker/pack/form',
|
||||||
groupLog: 'v1/groups/logs',
|
groupLog: 'v1/groups/logs',
|
||||||
|
@ -718,6 +718,9 @@ export type ProfileType = Readonly<{
|
||||||
unrestrictedUnidentifiedAccess?: string;
|
unrestrictedUnidentifiedAccess?: string;
|
||||||
uuid?: string;
|
uuid?: string;
|
||||||
credential?: string;
|
credential?: string;
|
||||||
|
|
||||||
|
// Only present when `credentialType` is `pni`
|
||||||
|
pniCredential?: string;
|
||||||
capabilities?: CapabilitiesType;
|
capabilities?: CapabilitiesType;
|
||||||
paymentAddress?: string;
|
paymentAddress?: string;
|
||||||
badges?: unknown;
|
badges?: unknown;
|
||||||
|
@ -769,7 +772,7 @@ export type GetUuidsForE164sV2OptionsType = Readonly<{
|
||||||
type GetProfileCommonOptionsType = Readonly<
|
type GetProfileCommonOptionsType = Readonly<
|
||||||
{
|
{
|
||||||
userLanguages: ReadonlyArray<string>;
|
userLanguages: ReadonlyArray<string>;
|
||||||
credentialType?: 'pni' | 'profileKey';
|
credentialType?: 'pni' | 'expiringProfileKey';
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
profileKeyVersion?: undefined;
|
profileKeyVersion?: undefined;
|
||||||
|
@ -792,6 +795,11 @@ export type GetProfileUnauthOptionsType = GetProfileCommonOptionsType &
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type GetGroupCredentialsOptionsType = Readonly<{
|
||||||
|
startDayInMs: number;
|
||||||
|
endDayInMs: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type WebAPIType = {
|
export type WebAPIType = {
|
||||||
startRegistration(): unknown;
|
startRegistration(): unknown;
|
||||||
finishRegistration(baton: unknown): void;
|
finishRegistration(baton: unknown): void;
|
||||||
|
@ -819,9 +827,7 @@ export type WebAPIType = {
|
||||||
) => Promise<Proto.GroupJoinInfo>;
|
) => Promise<Proto.GroupJoinInfo>;
|
||||||
getGroupAvatar: (key: string) => Promise<Uint8Array>;
|
getGroupAvatar: (key: string) => Promise<Uint8Array>;
|
||||||
getGroupCredentials: (
|
getGroupCredentials: (
|
||||||
startDay: number,
|
options: GetGroupCredentialsOptionsType
|
||||||
endDay: number,
|
|
||||||
uuidKind: UUIDKind
|
|
||||||
) => Promise<Array<GroupCredentialType>>;
|
) => Promise<Array<GroupCredentialType>>;
|
||||||
getGroupExternalCredential: (
|
getGroupExternalCredential: (
|
||||||
options: GroupCredentialsType
|
options: GroupCredentialsType
|
||||||
|
@ -1580,7 +1586,7 @@ export function initialize({
|
||||||
{
|
{
|
||||||
profileKeyVersion,
|
profileKeyVersion,
|
||||||
profileKeyCredentialRequest,
|
profileKeyCredentialRequest,
|
||||||
credentialType = 'profileKey',
|
credentialType = 'expiringProfileKey',
|
||||||
}: GetProfileCommonOptionsType
|
}: GetProfileCommonOptionsType
|
||||||
) {
|
) {
|
||||||
let profileUrl = `/${identifier}`;
|
let profileUrl = `/${identifier}`;
|
||||||
|
@ -2509,14 +2515,17 @@ export function initialize({
|
||||||
credentials: Array<GroupCredentialType>;
|
credentials: Array<GroupCredentialType>;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getGroupCredentials(
|
async function getGroupCredentials({
|
||||||
startDay: number,
|
startDayInMs,
|
||||||
endDay: number,
|
endDayInMs,
|
||||||
uuidKind: UUIDKind
|
}: GetGroupCredentialsOptionsType): Promise<Array<GroupCredentialType>> {
|
||||||
): Promise<Array<GroupCredentialType>> {
|
const startDayInSeconds = startDayInMs / durations.SECOND;
|
||||||
|
const endDayInSeconds = endDayInMs / durations.SECOND;
|
||||||
const response = (await _ajax({
|
const response = (await _ajax({
|
||||||
call: 'getGroupCredentials',
|
call: 'getGroupCredentials',
|
||||||
urlParameters: `/${startDay}/${endDay}?${uuidKindToQuery(uuidKind)}`,
|
urlParameters:
|
||||||
|
`?redemptionStartSeconds=${startDayInSeconds}&` +
|
||||||
|
`redemptionEndSeconds=${endDayInSeconds}`,
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
})) as CredentialResponseType;
|
})) as CredentialResponseType;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import type { PublicKey } from '@signalapp/libsignal-client';
|
import type { PublicKey } from '@signalapp/libsignal-client';
|
||||||
|
|
||||||
import type { SignalService as Proto } from '../protobuf';
|
import type { SignalService as Proto } from '../protobuf';
|
||||||
|
import type { UUIDStringType } from '../types/UUID';
|
||||||
import type {
|
import type {
|
||||||
ProcessedEnvelope,
|
ProcessedEnvelope,
|
||||||
ProcessedDataMessage,
|
ProcessedDataMessage,
|
||||||
|
@ -129,7 +130,7 @@ export type DeliveryEventData = Readonly<{
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
envelopeTimestamp: number;
|
envelopeTimestamp: number;
|
||||||
source?: string;
|
source?: string;
|
||||||
sourceUuid?: string;
|
sourceUuid?: UUIDStringType;
|
||||||
sourceDevice?: number;
|
sourceDevice?: number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
@ -166,7 +167,7 @@ export class DecryptionErrorEvent extends ConfirmableEvent {
|
||||||
export type RetryRequestEventData = Readonly<{
|
export type RetryRequestEventData = Readonly<{
|
||||||
groupId?: string;
|
groupId?: string;
|
||||||
ratchetKey?: PublicKey;
|
ratchetKey?: PublicKey;
|
||||||
requesterUuid: string;
|
requesterUuid: UUIDStringType;
|
||||||
requesterDevice: number;
|
requesterDevice: number;
|
||||||
senderDevice: number;
|
senderDevice: number;
|
||||||
sentAt: number;
|
sentAt: number;
|
||||||
|
@ -204,7 +205,7 @@ export class SentEvent extends ConfirmableEvent {
|
||||||
|
|
||||||
export type ProfileKeyUpdateData = Readonly<{
|
export type ProfileKeyUpdateData = Readonly<{
|
||||||
source?: string;
|
source?: string;
|
||||||
sourceUuid?: string;
|
sourceUuid?: UUIDStringType;
|
||||||
profileKey: string;
|
profileKey: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
@ -219,7 +220,7 @@ export class ProfileKeyUpdateEvent extends ConfirmableEvent {
|
||||||
|
|
||||||
export type MessageEventData = Readonly<{
|
export type MessageEventData = Readonly<{
|
||||||
source?: string;
|
source?: string;
|
||||||
sourceUuid?: string;
|
sourceUuid?: UUIDStringType;
|
||||||
sourceDevice?: number;
|
sourceDevice?: number;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
serverGuid?: string;
|
serverGuid?: string;
|
||||||
|
@ -243,7 +244,7 @@ export type ReadOrViewEventData = Readonly<{
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
envelopeTimestamp: number;
|
envelopeTimestamp: number;
|
||||||
source?: string;
|
source?: string;
|
||||||
sourceUuid?: string;
|
sourceUuid?: UUIDStringType;
|
||||||
sourceDevice?: number;
|
sourceDevice?: number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
@ -276,14 +277,14 @@ export class ConfigurationEvent extends ConfirmableEvent {
|
||||||
|
|
||||||
export type ViewOnceOpenSyncOptions = {
|
export type ViewOnceOpenSyncOptions = {
|
||||||
source?: string;
|
source?: string;
|
||||||
sourceUuid?: string;
|
sourceUuid?: UUIDStringType;
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ViewOnceOpenSyncEvent extends ConfirmableEvent {
|
export class ViewOnceOpenSyncEvent extends ConfirmableEvent {
|
||||||
public readonly source?: string;
|
public readonly source?: string;
|
||||||
|
|
||||||
public readonly sourceUuid?: string;
|
public readonly sourceUuid?: UUIDStringType;
|
||||||
|
|
||||||
public readonly timestamp?: number;
|
public readonly timestamp?: number;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
// Copyright 2020-2022 Signal Messenger, LLC
|
// Copyright 2020-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { ProfileKeyCredentialRequestContext } from '@signalapp/libsignal-client/zkgroup';
|
import type {
|
||||||
|
ProfileKeyCredentialRequestContext,
|
||||||
|
ClientZkProfileOperations,
|
||||||
|
} from '@signalapp/libsignal-client/zkgroup';
|
||||||
import { SEALED_SENDER } from '../types/SealedSender';
|
import { SEALED_SENDER } from '../types/SealedSender';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
import type {
|
import type {
|
||||||
|
@ -11,12 +14,15 @@ import type {
|
||||||
import { HTTPError } from '../textsecure/Errors';
|
import { HTTPError } from '../textsecure/Errors';
|
||||||
import { Address } from '../types/Address';
|
import { Address } from '../types/Address';
|
||||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||||
|
import { UUIDKind } from '../types/UUID';
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
import { trimForDisplay, verifyAccessKey, decryptProfile } from '../Crypto';
|
import { trimForDisplay, verifyAccessKey, decryptProfile } from '../Crypto';
|
||||||
import {
|
import {
|
||||||
generateProfileKeyCredentialRequest,
|
generateProfileKeyCredentialRequest,
|
||||||
|
generatePNICredentialRequest,
|
||||||
getClientZkProfileOperations,
|
getClientZkProfileOperations,
|
||||||
handleProfileKeyCredential,
|
handleProfileKeyCredential,
|
||||||
|
handleProfileKeyPNICredential,
|
||||||
} from './zkgroup';
|
} from './zkgroup';
|
||||||
import { isMe } from './whatTypeOfConversation';
|
import { isMe } from './whatTypeOfConversation';
|
||||||
import type { ConversationModel } from '../models/conversations';
|
import type { ConversationModel } from '../models/conversations';
|
||||||
|
@ -25,6 +31,68 @@ import { getUserLanguages } from './userLanguages';
|
||||||
import { parseBadgesFromServer } from '../badges/parseBadgesFromServer';
|
import { parseBadgesFromServer } from '../badges/parseBadgesFromServer';
|
||||||
import { strictAssert } from './assert';
|
import { strictAssert } from './assert';
|
||||||
|
|
||||||
|
async function maybeGetPNICredential(
|
||||||
|
c: ConversationModel,
|
||||||
|
{
|
||||||
|
clientZkProfileCipher,
|
||||||
|
profileKey,
|
||||||
|
profileKeyVersion,
|
||||||
|
userLanguages,
|
||||||
|
}: {
|
||||||
|
clientZkProfileCipher: ClientZkProfileOperations;
|
||||||
|
profileKey: string;
|
||||||
|
profileKeyVersion: string;
|
||||||
|
userLanguages: ReadonlyArray<string>;
|
||||||
|
}
|
||||||
|
): Promise<void> {
|
||||||
|
// Already present and up-to-date
|
||||||
|
if (c.get('pniCredential')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
strictAssert(isMe(c.attributes), 'Has to fetch PNI credential for ourselves');
|
||||||
|
|
||||||
|
log.info('maybeGetPNICredential: requesting PNI credential');
|
||||||
|
|
||||||
|
const { storage, messaging } = window.textsecure;
|
||||||
|
strictAssert(
|
||||||
|
messaging,
|
||||||
|
'maybeGetPNICredential: window.textsecure.messaging not available'
|
||||||
|
);
|
||||||
|
|
||||||
|
const ourACI = storage.user.getCheckedUuid(UUIDKind.ACI);
|
||||||
|
const ourPNI = storage.user.getCheckedUuid(UUIDKind.PNI);
|
||||||
|
|
||||||
|
const {
|
||||||
|
requestHex: profileKeyCredentialRequestHex,
|
||||||
|
context: profileCredentialRequestContext,
|
||||||
|
} = generatePNICredentialRequest(
|
||||||
|
clientZkProfileCipher,
|
||||||
|
ourACI.toString(),
|
||||||
|
ourPNI.toString(),
|
||||||
|
profileKey
|
||||||
|
);
|
||||||
|
|
||||||
|
const profile = await messaging.getProfile(ourACI, {
|
||||||
|
userLanguages,
|
||||||
|
profileKeyVersion,
|
||||||
|
profileKeyCredentialRequest: profileKeyCredentialRequestHex,
|
||||||
|
credentialType: 'pni',
|
||||||
|
});
|
||||||
|
|
||||||
|
strictAssert(
|
||||||
|
profile.pniCredential,
|
||||||
|
'We must get the credential for ourselves'
|
||||||
|
);
|
||||||
|
const pniCredential = handleProfileKeyPNICredential(
|
||||||
|
clientZkProfileCipher,
|
||||||
|
profileCredentialRequestContext,
|
||||||
|
profile.pniCredential
|
||||||
|
);
|
||||||
|
c.set({ pniCredential });
|
||||||
|
|
||||||
|
log.info('maybeGetPNICredential: updated PNI credential');
|
||||||
|
}
|
||||||
|
|
||||||
async function doGetProfile(c: ConversationModel): Promise<void> {
|
async function doGetProfile(c: ConversationModel): Promise<void> {
|
||||||
const idForLogging = c.idForLogging();
|
const idForLogging = c.idForLogging();
|
||||||
const { messaging } = window.textsecure;
|
const { messaging } = window.textsecure;
|
||||||
|
@ -168,6 +236,22 @@ async function doGetProfile(c: ConversationModel): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isMe(c.attributes) && profileKey && profileKeyVersion) {
|
||||||
|
try {
|
||||||
|
await maybeGetPNICredential(c, {
|
||||||
|
clientZkProfileCipher,
|
||||||
|
profileKey,
|
||||||
|
profileKeyVersion,
|
||||||
|
userLanguages,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
log.warn(
|
||||||
|
'getProfile failed to get our own PNI credential',
|
||||||
|
Errors.toLogFormat(error)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (profile.identityKey) {
|
if (profile.identityKey) {
|
||||||
const identityKey = Bytes.fromBase64(profile.identityKey);
|
const identityKey = Bytes.fromBase64(profile.identityKey);
|
||||||
const changed = await window.textsecure.storage.protocol.saveIdentity(
|
const changed = await window.textsecure.storage.protocol.saveIdentity(
|
||||||
|
@ -285,12 +369,15 @@ async function doGetProfile(c: ConversationModel): Promise<void> {
|
||||||
|
|
||||||
if (profileCredentialRequestContext) {
|
if (profileCredentialRequestContext) {
|
||||||
if (profile.credential) {
|
if (profile.credential) {
|
||||||
const profileKeyCredential = handleProfileKeyCredential(
|
const {
|
||||||
|
credential: profileKeyCredential,
|
||||||
|
expiration: profileKeyCredentialExpiration,
|
||||||
|
} = handleProfileKeyCredential(
|
||||||
clientZkProfileCipher,
|
clientZkProfileCipher,
|
||||||
profileCredentialRequestContext,
|
profileCredentialRequestContext,
|
||||||
profile.credential
|
profile.credential
|
||||||
);
|
);
|
||||||
c.set({ profileKeyCredential });
|
c.set({ profileKeyCredential, profileKeyCredentialExpiration });
|
||||||
} else {
|
} else {
|
||||||
c.unset('profileKeyCredential');
|
c.unset('profileKeyCredential');
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { parseIntOrThrow } from './parseIntOrThrow';
|
||||||
import * as RemoteConfig from '../RemoteConfig';
|
import * as RemoteConfig from '../RemoteConfig';
|
||||||
import { Address } from '../types/Address';
|
import { Address } from '../types/Address';
|
||||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||||
|
import { UUID } from '../types/UUID';
|
||||||
import { ToastDecryptionError } from '../components/ToastDecryptionError';
|
import { ToastDecryptionError } from '../components/ToastDecryptionError';
|
||||||
import { showToast } from './showToast';
|
import { showToast } from './showToast';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
|
@ -287,7 +288,7 @@ async function sendDistributionMessageOrNullMessage(
|
||||||
const group = window.ConversationController.get(groupId);
|
const group = window.ConversationController.get(groupId);
|
||||||
const distributionId = group?.get('senderKeyInfo')?.distributionId;
|
const distributionId = group?.get('senderKeyInfo')?.distributionId;
|
||||||
|
|
||||||
if (group && !group.hasMember(requesterUuid)) {
|
if (group && !group.hasMember(new UUID(requesterUuid))) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`sendDistributionMessageOrNullMessage/${logId}: Requester ${requesterUuid} is not a member of ${conversation.idForLogging()}`
|
`sendDistributionMessageOrNullMessage/${logId}: Requester ${requesterUuid} is not a member of ${conversation.idForLogging()}`
|
||||||
);
|
);
|
||||||
|
@ -429,7 +430,7 @@ async function maybeAddSenderKeyDistributionMessage({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!conversation.hasMember(requesterUuid)) {
|
if (!conversation.hasMember(new UUID(requesterUuid))) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`maybeAddSenderKeyDistributionMessage/${logId}: Recipient ${requesterUuid} is not a member of ${conversation.idForLogging()}`
|
`maybeAddSenderKeyDistributionMessage/${logId}: Recipient ${requesterUuid} is not a member of ${conversation.idForLogging()}`
|
||||||
);
|
);
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { Address } from '../types/Address';
|
||||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||||
import { UUID } from '../types/UUID';
|
import { UUID } from '../types/UUID';
|
||||||
import { getValue, isEnabled } from '../RemoteConfig';
|
import { getValue, isEnabled } from '../RemoteConfig';
|
||||||
|
import type { UUIDStringType } from '../types/UUID';
|
||||||
import { isRecord } from './isRecord';
|
import { isRecord } from './isRecord';
|
||||||
|
|
||||||
import { isOlderThan } from './timestamp';
|
import { isOlderThan } from './timestamp';
|
||||||
|
@ -80,7 +81,7 @@ const ZERO_ACCESS_KEY = Bytes.toBase64(new Uint8Array(ACCESS_KEY_LENGTH));
|
||||||
export type SenderKeyTargetType = {
|
export type SenderKeyTargetType = {
|
||||||
getGroupId: () => string | undefined;
|
getGroupId: () => string | undefined;
|
||||||
getMembers: () => Array<ConversationModel>;
|
getMembers: () => Array<ConversationModel>;
|
||||||
hasMember: (id: string) => boolean;
|
hasMember: (uuid: UUIDStringType) => boolean;
|
||||||
idForLogging: () => string;
|
idForLogging: () => string;
|
||||||
isGroupV2: () => boolean;
|
isGroupV2: () => boolean;
|
||||||
isValid: () => boolean;
|
isValid: () => boolean;
|
||||||
|
@ -1145,10 +1146,12 @@ function partialDeviceComparator(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUuidsFromDevices(devices: Array<DeviceType>): Array<string> {
|
function getUuidsFromDevices(
|
||||||
const uuids = new Set<string>();
|
devices: Array<DeviceType>
|
||||||
|
): Array<UUIDStringType> {
|
||||||
|
const uuids = new Set<UUIDStringType>();
|
||||||
devices.forEach(device => {
|
devices.forEach(device => {
|
||||||
uuids.add(device.identifier);
|
uuids.add(UUID.checkedLookup(device.identifier).toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
return Array.from(uuids);
|
return Array.from(uuids);
|
||||||
|
@ -1160,9 +1163,9 @@ export function _analyzeSenderKeyDevices(
|
||||||
isPartialSend?: boolean
|
isPartialSend?: boolean
|
||||||
): {
|
): {
|
||||||
newToMemberDevices: Array<DeviceType>;
|
newToMemberDevices: Array<DeviceType>;
|
||||||
newToMemberUuids: Array<string>;
|
newToMemberUuids: Array<UUIDStringType>;
|
||||||
removedFromMemberDevices: Array<DeviceType>;
|
removedFromMemberDevices: Array<DeviceType>;
|
||||||
removedFromMemberUuids: Array<string>;
|
removedFromMemberUuids: Array<UUIDStringType>;
|
||||||
} {
|
} {
|
||||||
const newToMemberDevices = differenceWith<DeviceType, DeviceType>(
|
const newToMemberDevices = differenceWith<DeviceType, DeviceType>(
|
||||||
devicesForSend,
|
devicesForSend,
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { ProfileKeyCredentialRequestContext } from '@signalapp/libsignal-client/zkgroup';
|
import type {
|
||||||
|
ProfileKeyCredentialRequestContext,
|
||||||
|
PniCredentialRequestContext,
|
||||||
|
} from '@signalapp/libsignal-client/zkgroup';
|
||||||
import {
|
import {
|
||||||
AuthCredential,
|
AuthCredentialWithPni,
|
||||||
ClientZkAuthOperations,
|
ClientZkAuthOperations,
|
||||||
ClientZkGroupCipher,
|
ClientZkGroupCipher,
|
||||||
ClientZkProfileOperations,
|
ClientZkProfileOperations,
|
||||||
|
@ -11,9 +14,12 @@ import {
|
||||||
GroupSecretParams,
|
GroupSecretParams,
|
||||||
ProfileKey,
|
ProfileKey,
|
||||||
ProfileKeyCiphertext,
|
ProfileKeyCiphertext,
|
||||||
ProfileKeyCredential,
|
ExpiringProfileKeyCredential,
|
||||||
ProfileKeyCredentialPresentation,
|
ProfileKeyCredentialPresentation,
|
||||||
ProfileKeyCredentialResponse,
|
ExpiringProfileKeyCredentialResponse,
|
||||||
|
PniCredential,
|
||||||
|
PniCredentialResponse,
|
||||||
|
PniCredentialPresentation,
|
||||||
ServerPublicParams,
|
ServerPublicParams,
|
||||||
UuidCiphertext,
|
UuidCiphertext,
|
||||||
NotarySignature,
|
NotarySignature,
|
||||||
|
@ -32,26 +38,45 @@ export function decryptGroupBlob(
|
||||||
return clientZkGroupCipher.decryptBlob(Buffer.from(ciphertext));
|
return clientZkGroupCipher.decryptBlob(Buffer.from(ciphertext));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decryptProfileKeyCredentialPresentation(
|
export function decodeProfileKeyCredentialPresentation(
|
||||||
clientZkGroupCipher: ClientZkGroupCipher,
|
|
||||||
presentationBuffer: Uint8Array
|
presentationBuffer: Uint8Array
|
||||||
): { profileKey: Uint8Array; uuid: UUIDStringType } {
|
): { profileKey: Uint8Array; userId: Uint8Array } {
|
||||||
const presentation = new ProfileKeyCredentialPresentation(
|
const presentation = new ProfileKeyCredentialPresentation(
|
||||||
Buffer.from(presentationBuffer)
|
Buffer.from(presentationBuffer)
|
||||||
);
|
);
|
||||||
|
|
||||||
const uuidCiphertext = presentation.getUuidCiphertext();
|
const userId = presentation.getUuidCiphertext().serialize();
|
||||||
const uuid = clientZkGroupCipher.decryptUuid(uuidCiphertext);
|
const profileKey = presentation.getProfileKeyCiphertext().serialize();
|
||||||
|
|
||||||
|
return {
|
||||||
|
profileKey,
|
||||||
|
userId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decryptPniCredentialPresentation(
|
||||||
|
clientZkGroupCipher: ClientZkGroupCipher,
|
||||||
|
presentationBuffer: Uint8Array
|
||||||
|
): { profileKey: Uint8Array; pni: UUIDStringType; aci: UUIDStringType } {
|
||||||
|
const presentation = new PniCredentialPresentation(
|
||||||
|
Buffer.from(presentationBuffer)
|
||||||
|
);
|
||||||
|
|
||||||
|
const pniCiphertext = presentation.getPniCiphertext();
|
||||||
|
const aciCiphertext = presentation.getAciCiphertext();
|
||||||
|
const aci = clientZkGroupCipher.decryptUuid(aciCiphertext);
|
||||||
|
const pni = clientZkGroupCipher.decryptUuid(pniCiphertext);
|
||||||
|
|
||||||
const profileKeyCiphertext = presentation.getProfileKeyCiphertext();
|
const profileKeyCiphertext = presentation.getProfileKeyCiphertext();
|
||||||
const profileKey = clientZkGroupCipher.decryptProfileKey(
|
const profileKey = clientZkGroupCipher.decryptProfileKey(
|
||||||
profileKeyCiphertext,
|
profileKeyCiphertext,
|
||||||
uuid
|
aci
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
profileKey: profileKey.serialize(),
|
profileKey: profileKey.serialize(),
|
||||||
uuid: UUID.cast(uuid),
|
aci: UUID.cast(aci),
|
||||||
|
pni: UUID.cast(pni),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,9 +154,11 @@ export function encryptGroupBlob(
|
||||||
|
|
||||||
export function encryptUuid(
|
export function encryptUuid(
|
||||||
clientZkGroupCipher: ClientZkGroupCipher,
|
clientZkGroupCipher: ClientZkGroupCipher,
|
||||||
uuidPlaintext: UUIDStringType
|
uuidPlaintext: UUID
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
const uuidCiphertext = clientZkGroupCipher.encryptUuid(uuidPlaintext);
|
const uuidCiphertext = clientZkGroupCipher.encryptUuid(
|
||||||
|
uuidPlaintext.toString()
|
||||||
|
);
|
||||||
|
|
||||||
return uuidCiphertext.serialize();
|
return uuidCiphertext.serialize();
|
||||||
}
|
}
|
||||||
|
@ -158,22 +185,46 @@ export function generateProfileKeyCredentialRequest(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generatePNICredentialRequest(
|
||||||
|
clientZkProfileCipher: ClientZkProfileOperations,
|
||||||
|
aci: UUIDStringType,
|
||||||
|
pni: UUIDStringType,
|
||||||
|
profileKeyBase64: string
|
||||||
|
): { context: PniCredentialRequestContext; requestHex: string } {
|
||||||
|
const profileKeyArray = Buffer.from(profileKeyBase64, 'base64');
|
||||||
|
const profileKey = new ProfileKey(profileKeyArray);
|
||||||
|
|
||||||
|
const context = clientZkProfileCipher.createPniCredentialRequestContext(
|
||||||
|
aci,
|
||||||
|
pni,
|
||||||
|
profileKey
|
||||||
|
);
|
||||||
|
const request = context.getRequest();
|
||||||
|
const requestArray = request.serialize();
|
||||||
|
|
||||||
|
return {
|
||||||
|
context,
|
||||||
|
requestHex: requestArray.toString('hex'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function getAuthCredentialPresentation(
|
export function getAuthCredentialPresentation(
|
||||||
clientZkAuthOperations: ClientZkAuthOperations,
|
clientZkAuthOperations: ClientZkAuthOperations,
|
||||||
authCredentialBase64: string,
|
authCredentialBase64: string,
|
||||||
groupSecretParamsBase64: string
|
groupSecretParamsBase64: string
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
const authCredential = new AuthCredential(
|
const authCredential = new AuthCredentialWithPni(
|
||||||
Buffer.from(authCredentialBase64, 'base64')
|
Buffer.from(authCredentialBase64, 'base64')
|
||||||
);
|
);
|
||||||
const secretParams = new GroupSecretParams(
|
const secretParams = new GroupSecretParams(
|
||||||
Buffer.from(groupSecretParamsBase64, 'base64')
|
Buffer.from(groupSecretParamsBase64, 'base64')
|
||||||
);
|
);
|
||||||
|
|
||||||
const presentation = clientZkAuthOperations.createAuthCredentialPresentation(
|
const presentation =
|
||||||
secretParams,
|
clientZkAuthOperations.createAuthCredentialWithPniPresentation(
|
||||||
authCredential
|
secretParams,
|
||||||
);
|
authCredential
|
||||||
|
);
|
||||||
return presentation.serialize();
|
return presentation.serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,7 +237,7 @@ export function createProfileKeyCredentialPresentation(
|
||||||
profileKeyCredentialBase64,
|
profileKeyCredentialBase64,
|
||||||
'base64'
|
'base64'
|
||||||
);
|
);
|
||||||
const profileKeyCredential = new ProfileKeyCredential(
|
const profileKeyCredential = new ExpiringProfileKeyCredential(
|
||||||
profileKeyCredentialArray
|
profileKeyCredentialArray
|
||||||
);
|
);
|
||||||
const secretParams = new GroupSecretParams(
|
const secretParams = new GroupSecretParams(
|
||||||
|
@ -194,7 +245,7 @@ export function createProfileKeyCredentialPresentation(
|
||||||
);
|
);
|
||||||
|
|
||||||
const presentation =
|
const presentation =
|
||||||
clientZkProfileCipher.createProfileKeyCredentialPresentation(
|
clientZkProfileCipher.createExpiringProfileKeyCredentialPresentation(
|
||||||
secretParams,
|
secretParams,
|
||||||
profileKeyCredential
|
profileKeyCredential
|
||||||
);
|
);
|
||||||
|
@ -202,6 +253,25 @@ export function createProfileKeyCredentialPresentation(
|
||||||
return presentation.serialize();
|
return presentation.serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createPNICredentialPresentation(
|
||||||
|
clientZkProfileCipher: ClientZkProfileOperations,
|
||||||
|
pniCredentialBase64: string,
|
||||||
|
groupSecretParamsBase64: string
|
||||||
|
): Uint8Array {
|
||||||
|
const pniCredentialArray = Buffer.from(pniCredentialBase64, 'base64');
|
||||||
|
const pniCredential = new PniCredential(pniCredentialArray);
|
||||||
|
const secretParams = new GroupSecretParams(
|
||||||
|
Buffer.from(groupSecretParamsBase64, 'base64')
|
||||||
|
);
|
||||||
|
|
||||||
|
const presentation = clientZkProfileCipher.createPniCredentialPresentation(
|
||||||
|
secretParams,
|
||||||
|
pniCredential
|
||||||
|
);
|
||||||
|
|
||||||
|
return presentation.serialize();
|
||||||
|
}
|
||||||
|
|
||||||
export function getClientZkAuthOperations(
|
export function getClientZkAuthOperations(
|
||||||
serverPublicParamsBase64: string
|
serverPublicParamsBase64: string
|
||||||
): ClientZkAuthOperations {
|
): ClientZkAuthOperations {
|
||||||
|
@ -236,15 +306,39 @@ export function handleProfileKeyCredential(
|
||||||
clientZkProfileCipher: ClientZkProfileOperations,
|
clientZkProfileCipher: ClientZkProfileOperations,
|
||||||
context: ProfileKeyCredentialRequestContext,
|
context: ProfileKeyCredentialRequestContext,
|
||||||
responseBase64: string
|
responseBase64: string
|
||||||
): string {
|
): { credential: string; expiration: number } {
|
||||||
const response = new ProfileKeyCredentialResponse(
|
const response = new ExpiringProfileKeyCredentialResponse(
|
||||||
Buffer.from(responseBase64, 'base64')
|
Buffer.from(responseBase64, 'base64')
|
||||||
);
|
);
|
||||||
const profileKeyCredential =
|
const profileKeyCredential =
|
||||||
clientZkProfileCipher.receiveProfileKeyCredential(context, response);
|
clientZkProfileCipher.receiveExpiringProfileKeyCredential(
|
||||||
|
context,
|
||||||
|
response
|
||||||
|
);
|
||||||
|
|
||||||
const credentialArray = profileKeyCredential.serialize();
|
const credentialArray = profileKeyCredential.serialize();
|
||||||
|
|
||||||
|
return {
|
||||||
|
credential: credentialArray.toString('base64'),
|
||||||
|
expiration: profileKeyCredential.getExpirationTime().getTime(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleProfileKeyPNICredential(
|
||||||
|
clientZkProfileCipher: ClientZkProfileOperations,
|
||||||
|
context: PniCredentialRequestContext,
|
||||||
|
responseBase64: string
|
||||||
|
): string {
|
||||||
|
const response = new PniCredentialResponse(
|
||||||
|
Buffer.from(responseBase64, 'base64')
|
||||||
|
);
|
||||||
|
const pniCredential = clientZkProfileCipher.receivePniCredential(
|
||||||
|
context,
|
||||||
|
response
|
||||||
|
);
|
||||||
|
|
||||||
|
const credentialArray = pniCredential.serialize();
|
||||||
|
|
||||||
return credentialArray.toString('base64');
|
return credentialArray.toString('base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1263,7 +1263,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
const ourUuid = window.textsecure.storage.user.getUuid(UUIDKind.ACI);
|
const ourUuid = window.textsecure.storage.user.getUuid(UUIDKind.ACI);
|
||||||
if (
|
if (
|
||||||
!isGroup(this.model.attributes) ||
|
!isGroup(this.model.attributes) ||
|
||||||
(ourUuid && this.model.hasMember(ourUuid.toString()))
|
(ourUuid && this.model.hasMember(ourUuid))
|
||||||
) {
|
) {
|
||||||
strictAssert(
|
strictAssert(
|
||||||
this.model.throttledGetProfiles !== undefined,
|
this.model.throttledGetProfiles !== undefined,
|
||||||
|
|
26
yarn.lock
26
yarn.lock
|
@ -1745,28 +1745,20 @@
|
||||||
"@react-spring/shared" "~9.4.5"
|
"@react-spring/shared" "~9.4.5"
|
||||||
"@react-spring/types" "~9.4.5"
|
"@react-spring/types" "~9.4.5"
|
||||||
|
|
||||||
"@signalapp/libsignal-client@0.17.0":
|
"@signalapp/libsignal-client@0.18.1", "@signalapp/libsignal-client@^0.18.1":
|
||||||
version "0.17.0"
|
version "0.18.1"
|
||||||
resolved "https://registry.yarnpkg.com/@signalapp/libsignal-client/-/libsignal-client-0.17.0.tgz#ffe6763d80f56148b45192bca29deb16f9a0aea8"
|
resolved "https://registry.yarnpkg.com/@signalapp/libsignal-client/-/libsignal-client-0.18.1.tgz#6b499cdcc952f1981c6367f68484cf3275be3b31"
|
||||||
integrity sha512-O5bd/BURWnybh6KhRYSO3NmNb1/oySu5yJx5ELy3QsfeFvpMnTkr0/PcXd0MCvRiaoN+/a0TsnywMO43t6Nxsw==
|
integrity sha512-43NcTYpahImlWHBDaNFmn7QaeXZHkFkTtb4m+ZWgzU0mkS1M8V+orGen2XuDvNiu+9HQmW4Lg7FV1deXhWtIRA==
|
||||||
dependencies:
|
dependencies:
|
||||||
node-gyp-build "^4.2.3"
|
node-gyp-build "^4.2.3"
|
||||||
uuid "^8.3.0"
|
uuid "^8.3.0"
|
||||||
|
|
||||||
"@signalapp/libsignal-client@^0.16.0":
|
"@signalapp/mock-server@2.0.1":
|
||||||
version "0.16.0"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@signalapp/libsignal-client/-/libsignal-client-0.16.0.tgz#7acba54b7ba05f513cdcf7f555efa1ccc6ce0145"
|
resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-2.0.1.tgz#0ecee7a0060181546e6b0c1b8e8c6f361fb2d7fe"
|
||||||
integrity sha512-/5EzlAcQoQReDomqV6VTtin5tvqvdUxoe8knSiz+L1kcLSlHA0So0zTR9WAdfQQ69t4q69vhaS4pu5yVI28YHA==
|
integrity sha512-YB0MIUzW8D1NirKpxxNXgEYuvK/OWbFo3djsBA4GqEUBIsJmdYcd4auHSqV3gKE/eSRoFQ0Z//eJNiqtsHbSEw==
|
||||||
dependencies:
|
dependencies:
|
||||||
node-gyp-build "^4.2.3"
|
"@signalapp/libsignal-client" "^0.18.1"
|
||||||
uuid "^8.3.0"
|
|
||||||
|
|
||||||
"@signalapp/mock-server@1.5.1":
|
|
||||||
version "1.5.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-1.5.1.tgz#e37a4505c037a3e85901cd00443d565cf9c2fe90"
|
|
||||||
integrity sha512-PqRrLhGPtKoTOeHj/L4tUlNkwXZ8MJMU3G7DaaVRAD+g+bpjpeb/ru73iH35K209wRdn4s7/hGUMaeRd6yKFxA==
|
|
||||||
dependencies:
|
|
||||||
"@signalapp/libsignal-client" "^0.16.0"
|
|
||||||
debug "^4.3.2"
|
debug "^4.3.2"
|
||||||
long "^4.0.0"
|
long "^4.0.0"
|
||||||
micro "^9.3.4"
|
micro "^9.3.4"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue