Merge remote-tracking branch 'upstream/clearnet' into voice-message-record-mp3
This commit is contained in:
commit
2e29206c99
|
@ -1,39 +0,0 @@
|
|||
indexeddb
|
||||
|
||||
|
||||
remove from db
|
||||
* 'sentSessionsTimestamp'
|
||||
* 'processedSessionsTimestamp'
|
||||
* 'sessions'
|
||||
* 'preKeys'
|
||||
* 'signedPreKeys'
|
||||
* senderkeys
|
||||
|
||||
getContact()
|
||||
remove what is is Storage / user.js
|
||||
remove on the UI ts files the calls to conversationModel. everything should be on the props
|
||||
conversationModel
|
||||
.get()
|
||||
|
||||
getOurNumber
|
||||
primaryDevicePubKey
|
||||
getRecipients() does not make asny sense right
|
||||
|
||||
ReadSyncs
|
||||
SyncMessage
|
||||
sendSyncMessage needs to be rewritten
|
||||
sendSyncMessageOnly to fix
|
||||
|
||||
|
||||
indexedDB
|
||||
initializeAttachmentMetadata=>
|
||||
run_migration
|
||||
|
||||
|
||||
|
||||
### Bug fixes on update of models
|
||||
* quote of attachment does not share preview
|
||||
* setting disappearing timer for isMe does not trigger the message
|
||||
* expiration timer set from user1 second device is not synced to his other devices for a private chat
|
||||
* add a way for users to know when the messageQueue is reconnected (loading bar or something)
|
||||
* handled better reconnection
|
|
@ -11,7 +11,7 @@ for it or creating a new one yourself. You can use also that issue as a place to
|
|||
your intentions and get feedback from the users most likely to appreciate your changes.
|
||||
|
||||
You're most likely to have your pull request accepted easily if it addresses bugs already
|
||||
in the [Next Steps project](https://github.com/loki-project/session-desktop/projects/1),
|
||||
in the [Next Steps project](https://github.com/oxen-io/session-desktop/projects/1),
|
||||
especially if they are near the top of the Backlog column. Those are what we'll be looking
|
||||
at next, so it would be great if you helped us out!
|
||||
|
||||
|
@ -22,7 +22,7 @@ ounce of prevention, as they say!](https://www.goodreads.com/quotes/247269-an-ou
|
|||
## Developer Setup
|
||||
|
||||
First, you'll need [Node.js](https://nodejs.org/) which matches our current version.
|
||||
You can check [`.nvmrc` in the `development` branch](https://github.com/loki-project/session-desktop/blob/development/.nvmrc) to see what the current version is. If you have [nvm](https://github.com/creationix/nvm)
|
||||
You can check [`.nvmrc` in the `clearnet` branch](https://github.com/oxen-io/session-desktop/blob/clearnet/.nvmrc) to see what the current version is. If you have [nvm](https://github.com/creationix/nvm)
|
||||
you can just run `nvm use` in the project directory and it will switch to the project's
|
||||
desired Node.js version. [nvm for windows](https://github.com/coreybutler/nvm-windows) is
|
||||
still useful, but it doesn't support `.nvmrc` files.
|
||||
|
@ -56,7 +56,7 @@ Then you need `git`, if you don't have that yet: https://git-scm.com/
|
|||
Now, run these commands in your preferred terminal in a good directory for development:
|
||||
|
||||
```
|
||||
git clone https://github.com/loki-project/session-desktop.git
|
||||
git clone https://github.com/oxen-io/session-desktop.git
|
||||
cd session-desktop
|
||||
npm install --global yarn # (only if you don’t already have `yarn`)
|
||||
yarn install --frozen-lockfile # Install and build dependencies (this will take a while)
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
## Summary
|
||||
|
||||
Session integrates directly with [Loki Service Nodes](https://lokidocs.com/ServiceNodes/SNOverview/), which are a set of distributed, decentralized and Sybil resistant nodes. Service Nodes act as servers which store messages offline, and a set of nodes which allow for onion routing functionality obfuscating users IP Addresses. For a full understanding of how Session works, read the [Session Whitepaper](https://getsession.org/whitepaper).
|
||||
Session integrates directly with [Oxen Service Nodes](https://docs.oxen.io/about-the-oxen-blockchain/oxen-service-nodes), which are a set of distributed, decentralized and Sybil resistant nodes. Service Nodes act as servers which store messages offline, and a set of nodes which allow for onion routing functionality obfuscating users IP Addresses. For a full understanding of how Session works, read the [Session Whitepaper](https://getsession.org/whitepaper).
|
||||
<br/><br/>
|
||||
![DesktopSession](https://i.imgur.com/ZnHvYjo.jpg)
|
||||
|
||||
## Want to Contribute? Found a Bug or Have a feature request?
|
||||
|
||||
Please search for any [existing issues](https://github.com/loki-project/session-desktop/issues) that describe your bugs in order to avoid duplicate submissions. <br><br>Submissions can be made by making a pull request to our development branch. If you don't know where to start contributing, try reading the Github issues page for ideas.
|
||||
Please search for any [existing issues](https://github.com/oxen-io/session-desktop/issues) that describe your bugs in order to avoid duplicate submissions. <br><br>Submissions can be made by making a pull request to our development branch. If you don't know where to start contributing, try reading the Github issues page for ideas.
|
||||
|
||||
## Build instruction
|
||||
|
||||
|
|
|
@ -453,6 +453,10 @@
|
|||
"unverify": {
|
||||
"message": "Mark As Not Verified"
|
||||
},
|
||||
"markAllAsRead": {
|
||||
"message": "Mark All as Read",
|
||||
"description": "Shown on a menu to mark the whole convo as read."
|
||||
},
|
||||
"isNotVerified": {
|
||||
"message": "You have not verified your safety number with $name$.",
|
||||
"description": "Summary state shown at top of the safety number screen if user has not verified contact.",
|
||||
|
@ -1062,6 +1066,10 @@
|
|||
"description": "Copy to clipboard session ID",
|
||||
"androidKey": "activity_conversation_menu_copy_session_id"
|
||||
},
|
||||
"copyOpenGroupURL": {
|
||||
"message": "Copy Group's URL",
|
||||
"description": "Copy to clipboard Open Group URL"
|
||||
},
|
||||
"save": {
|
||||
"message": "Save",
|
||||
"description": "Used as a 'commit changes' button in the Caption Editor for outgoing image attachments",
|
||||
|
|
|
@ -363,6 +363,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"markAllAsRead": {
|
||||
"message": "Tout Marquer Comme Lu",
|
||||
"description": "Shown on a menu to mark the whole convo as read."
|
||||
},
|
||||
"isNotVerified": {
|
||||
"message": "Vous n’avez pas vérifié votre numéro de sécurité avec $name$",
|
||||
"description": "Summary state shown at top of the safety number screen if user has not verified contact.",
|
||||
|
@ -1221,6 +1225,14 @@
|
|||
"copy": {
|
||||
"message": "Copier"
|
||||
},
|
||||
"copySessionID": {
|
||||
"message": "Copier le Session ID",
|
||||
"description": "Copy to clipboard session ID"
|
||||
},
|
||||
"copyOpenGroupURL": {
|
||||
"message": "Copier l'URL de Group",
|
||||
"description": "Copy to clipboard Open Group URL"
|
||||
},
|
||||
"linkPreviewsTitle": {
|
||||
"message": "Envoyer des aperçus de liens"
|
||||
},
|
||||
|
|
|
@ -33,17 +33,18 @@
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img src='images/session/session_icon_64.png' width='250' height='250'>
|
||||
<img src='images/session/session_icon.png' width='250' height='250'>
|
||||
|
||||
<div class='version'></div>
|
||||
<div class='commitHash'></div>
|
||||
<div class='environment'></div>
|
||||
<div>
|
||||
<a href="https://getsession.org">loki.network</a>
|
||||
<a href="https://getsession.org">getsession.org</a>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<a class="privacy" href="https://getsession.org">Terms & Privacy Policy</a>
|
||||
<a class="privacy" href="https://getsession.org/privacy-policy">Privacy Policy</a><br />
|
||||
<a class="privacy" href="https://getsession.org/terms-of-service/">Terms of Service</a>
|
||||
</div>
|
||||
|
||||
<script type='text/javascript' src='node_modules/jquery/dist/jquery.js'></script>
|
||||
|
|
|
@ -23,7 +23,6 @@ if (environment === 'production') {
|
|||
process.env.HOSTNAME = '';
|
||||
process.env.ALLOW_CONFIG_MUTATIONS = '';
|
||||
process.env.SUPPRESS_NO_CONFIG_WARNING = '';
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '';
|
||||
|
||||
// We could be running againt production but still be in dev mode, we need to handle that
|
||||
if (!isDevelopment) {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEEzCCAvugAwIBAgIUY9RQqbjhsQEkdeSgV9L0os9xZ7AwDQYJKoZIhvcNAQEL
|
||||
BQAwfDELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN
|
||||
ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x
|
||||
HzAdBgNVBAMMFnB1YmxpYy5sb2tpLmZvdW5kYXRpb24wHhcNMjEwNDA3MDExMDMx
|
||||
WhcNMjMwNDA3MDExMDMxWjB8MQswCQYDVQQGEwJBVTERMA8GA1UECAwIVmljdG9y
|
||||
aWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2YWN5IFRl
|
||||
Y2ggRm91bmRhdGlvbjEfMB0GA1UEAwwWcHVibGljLmxva2kuZm91bmRhdGlvbjCC
|
||||
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM5dBJSIR5+VNNUxUOo6FG0e
|
||||
RmZteRqBt50KXGbOi2A23a6sa57pLFh9Yw3hmlWV+QCL7ipG1X4IC55OStgoesf+
|
||||
K65VwEMP6Mtq0sSJS3R5TiuV2ZSRdSZTVjUyRXVe5T4Aw6wXVTAbc/HsyS780tDh
|
||||
GclfDHhonPhZpmTAnSbfMOS+BfOnBNvDxdto0kVh6k5nrGlkT4ECloulHTQF2lwJ
|
||||
0D6IOtv9AJplPdg6s2c4dY7durOdvr3NNVfvn5PTeRvbEPqzZur4WUUKIPNGu6mY
|
||||
PxImqd4eUsL0Vod4aAsTIx4YMmCTi0m9W6zJI6nXcK/6a+iiA3+NTNMzEA9gQhEC
|
||||
AwEAAaOBjDCBiTAdBgNVHQ4EFgQU/zahokxLvvFUpbnM6z/pwS1KsvwwHwYDVR0j
|
||||
BBgwFoAU/zahokxLvvFUpbnM6z/pwS1KsvwwDwYDVR0TAQH/BAUwAwEB/zAhBgNV
|
||||
HREEGjAYghZwdWJsaWMubG9raS5mb3VuZGF0aW9uMBMGA1UdJQQMMAoGCCsGAQUF
|
||||
BwMBMA0GCSqGSIb3DQEBCwUAA4IBAQBql+JvoqpaYrFFTOuDn08U+pdcd3GM7tbI
|
||||
zRH5LU+YnIpp9aRheek+2COW8DXsIy/kUngETCMLmX6ZaUj/WdHnTDkB0KTgxSHv
|
||||
ad3ZznKPKZ26qJOklr+0ZWj4J3jHbisSzql6mqq7R2Kp4ESwzwqxvkbykM5RUnmz
|
||||
Go/3Ol7bpN/ZVwwEkGfD/5rRHf57E/gZn2pBO+zotlQgr7HKRsIXQ2hIXVQqWmPQ
|
||||
lvfIwrwAZlfES7BARFnHOpyVQxV8uNcV5K5eXzuVFjHBqvq+BtyGhWkP9yKJCHS9
|
||||
OUXxch0rzRsH2C/kRVVhEk0pI3qlFiRC8pCJs98SNE9l69EQtG7I
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,25 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAwmgAwIBAgIUJsox1ZQPK/6iDsCC+MUJfNAlFuYwDQYJKoZIhvcNAQEL
|
||||
BQAwgYAxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0b3JpYTESMBAGA1UEBwwJ
|
||||
TWVsYm91cm5lMSUwIwYDVQQKDBxPeGVuIFByaXZhY3kgVGVjaCBGb3VuZGF0aW9u
|
||||
MSMwIQYDVQQDDBpzdG9yYWdlLnNlZWQxLmxva2kubmV0d29yazAeFw0yMTA0MDcw
|
||||
MTE5MjZaFw0yMzA0MDcwMTE5MjZaMIGAMQswCQYDVQQGEwJBVTERMA8GA1UECAwI
|
||||
VmljdG9yaWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2
|
||||
YWN5IFRlY2ggRm91bmRhdGlvbjEjMCEGA1UEAwwac3RvcmFnZS5zZWVkMS5sb2tp
|
||||
Lm5ldHdvcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtWH3Rz8Dd
|
||||
kEmM7tcBWHrJ/G8drr/+qidboEVYzxpyRjszaDxKXVhx4eBBsAD5RuCWuTuZmM8k
|
||||
TKEDLtf8xfb5SQ7YNX+346s9NXS5Poy4CIPASiW/QWXgIHFbVdv2hC+cKOP61OLM
|
||||
OGnOxfig6tQyd6EaCkedpY1DvSa2lPnQSOwC/jXCx6Vboc0zTY5R2bHtNc9hjIFP
|
||||
F4VClLAQSh2F4R1V9MH5KZMW+CCP6oaJY658W9JYXYRwlLrL2EFOVxHgcxq/6+fw
|
||||
+axXK9OXJrGZjuA+hiz+L/uAOtE4WuxrSeuNMHSrMtM9QqVn4bBuMJ21mAzfNoMP
|
||||
OIwgMT9DwUjVAgMBAAGjgZAwgY0wHQYDVR0OBBYEFOubJp9SoXIw+ONiWgkOaW8K
|
||||
zI/TMB8GA1UdIwQYMBaAFOubJp9SoXIw+ONiWgkOaW8KzI/TMA8GA1UdEwEB/wQF
|
||||
MAMBAf8wJQYDVR0RBB4wHIIac3RvcmFnZS5zZWVkMS5sb2tpLm5ldHdvcmswEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAIiHNhNrjYvwXVWs
|
||||
gacx8T/dpqpu9GE3L17LotgQr4R+IYHpNtcmwOTdtWWFfUTr75OCs+c3DqgRKEoj
|
||||
lnULOsVcalpAGIvW15/fmZWOf66Dpa4+ljDmAc3SOQiD0gGNtqblgI5zG1HF38QP
|
||||
hjYRhCZ5CVeGOLucvQ8tVVwQvArPFIkBr0jH9jHVgRWEI2MeI3FsU2H93D4TfGln
|
||||
N4SmmCfYBqygaaZBWkJEt0bYhn8uGHdU9UY9L2FPtfHVKkmFgO7cASGlvXS7B/TT
|
||||
/8IgbtM3O8mZc2asmdQhGwoAKz93ryyCd8X2UZJg/IwCSCayOlYZWY2fR4OPQmmV
|
||||
gxJsm+g=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,25 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAwmgAwIBAgIUc486Dy9Y00bUFfDeYmJIgSS5xREwDQYJKoZIhvcNAQEL
|
||||
BQAwgYAxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0b3JpYTESMBAGA1UEBwwJ
|
||||
TWVsYm91cm5lMSUwIwYDVQQKDBxPeGVuIFByaXZhY3kgVGVjaCBGb3VuZGF0aW9u
|
||||
MSMwIQYDVQQDDBpzdG9yYWdlLnNlZWQzLmxva2kubmV0d29yazAeFw0yMTA0MDcw
|
||||
MTIwNTJaFw0yMzA0MDcwMTIwNTJaMIGAMQswCQYDVQQGEwJBVTERMA8GA1UECAwI
|
||||
VmljdG9yaWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2
|
||||
YWN5IFRlY2ggRm91bmRhdGlvbjEjMCEGA1UEAwwac3RvcmFnZS5zZWVkMy5sb2tp
|
||||
Lm5ldHdvcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtokMlsFzf
|
||||
piYeD0EVNikMyvjltpF6fUEde9NOVrTtNTQT6kkDk+/0HF5LYgPaatv6v7fpUQHi
|
||||
kIwd6F0LTRGeWDFdsaWMdtlR1n/GxLPrOROsE8dcLt6GLavPf9rDabgva93m/JD6
|
||||
XW+Ne+MPEwqS8dAmFGhZd0gju6AtKFoSHnIf5pSQN6fSZUF/JQtHLVprAKKWKDiS
|
||||
ZwmWbmrZR2aofLD/VRpetabajnZlv9EeWloQwvUsw1C1hkAmmtFeeXtg7ePwrOzo
|
||||
6CnmcUJwOmi+LWqQV4A+58RZPFKaZoC5pzaKd0OYB8eZ8HB1F41UjGJgheX5Cyl4
|
||||
+amfF3l8dSq1AgMBAAGjgZAwgY0wHQYDVR0OBBYEFM9VSq4pGydjtX92Beul4+ml
|
||||
jBKtMB8GA1UdIwQYMBaAFM9VSq4pGydjtX92Beul4+mljBKtMA8GA1UdEwEB/wQF
|
||||
MAMBAf8wJQYDVR0RBB4wHIIac3RvcmFnZS5zZWVkMy5sb2tpLm5ldHdvcmswEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAAYxmhhkcKE1n6g1
|
||||
JqOa3UCBo4EfbqY5+FDZ0FVqv/cwemwVpKLbe6luRIS8poomdPCyMOS45V7wN3H9
|
||||
cFpfJ1TW19ydPVKmCXrl29ngmnY1q7YDwE/4qi3VK/UiqDkTHMKWjVPkenOyi8u6
|
||||
VVQANXSnKrn6GtigNFjGyD38O+j7AUSXBtXOJczaoF6r6BWgwQZ2WmgjuwvKTWSN
|
||||
4r8uObERoAQYVaeXfgdr4e9X/JdskBDaLFfoW/rrSozHB4FqVNFW96k+aIUgRa5p
|
||||
9kv115QcBPCSh9qOyTHij4tswS6SyOFaiKrNC4hgHQXP4QgioKmtsR/2Y+qJ6ddH
|
||||
6oo+4QU=
|
||||
-----END CERTIFICATE-----
|
|
@ -6,16 +6,16 @@
|
|||
"defaultPoWDifficulty": "1",
|
||||
"seedNodeList": [
|
||||
{
|
||||
"ip_url": "http://116.203.53.213/",
|
||||
"url": "https://storage.seed1.loki.network/"
|
||||
"ip_url": "http://116.203.53.213:4433/",
|
||||
"url": "https://storage.seed1.loki.network:4433/"
|
||||
},
|
||||
{
|
||||
"ip_url": "http://212.199.114.66/",
|
||||
"url": "https://storage.seed3.loki.network/"
|
||||
"ip_url": "http://212.199.114.66:4433/",
|
||||
"url": "https://storage.seed3.loki.network:4433/"
|
||||
},
|
||||
{
|
||||
"ip_url": "http://144.76.164.202/",
|
||||
"url": "https://public.loki.foundation/"
|
||||
"ip_url": "http://144.76.164.202:4433/",
|
||||
"url": "https://public.loki.foundation:4433/"
|
||||
}
|
||||
],
|
||||
"updatesEnabled": false,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* global window */
|
||||
|
||||
const FormData = require('form-data');
|
||||
const fetch = require('node-fetch');
|
||||
const insecureNodeFetch = require('node-fetch');
|
||||
|
||||
const BASE_URL = 'https://debuglogs.org';
|
||||
const VERSION = window.getVersion();
|
||||
|
@ -10,7 +10,8 @@ const USER_AGENT = `Session ${VERSION}`;
|
|||
|
||||
// upload :: String -> Promise URL
|
||||
exports.upload = async content => {
|
||||
const signedForm = await fetch(BASE_URL, {
|
||||
window.log.warn('insecureNodeFetch => upload debugLogs');
|
||||
const signedForm = await insecureNodeFetch(BASE_URL, {
|
||||
headers: {
|
||||
'user-agent': USER_AGENT,
|
||||
},
|
||||
|
@ -38,7 +39,7 @@ exports.upload = async content => {
|
|||
filename: `session-desktop-debug-log-${VERSION}.txt`,
|
||||
});
|
||||
|
||||
const result = await fetch(url, {
|
||||
const result = await insecureNodeFetch(url, {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* global log, textsecure, libloki, Signal, Whisper,
|
||||
clearTimeout, getMessageController, libsignal, StringView, window, _,
|
||||
dcodeIO, Buffer, process */
|
||||
const nodeFetch = require('node-fetch');
|
||||
const insecureNodeFetch = require('node-fetch');
|
||||
const { URL, URLSearchParams } = require('url');
|
||||
const FormData = require('form-data');
|
||||
const https = require('https');
|
||||
|
@ -253,7 +253,7 @@ const serverRequest = async (endpoint, options = {}) => {
|
|||
let response;
|
||||
let result;
|
||||
let txtResponse;
|
||||
let mode = 'nodeFetch';
|
||||
let mode = 'insecureNodeFetch';
|
||||
try {
|
||||
const host = url.host.toLowerCase();
|
||||
// log.info('host', host, FILESERVER_HOSTS);
|
||||
|
@ -268,7 +268,12 @@ const serverRequest = async (endpoint, options = {}) => {
|
|||
fetchOptions,
|
||||
options
|
||||
));
|
||||
} else if (window.lokiFeatureFlags.useFileOnionRequests && srvPubKey) {
|
||||
} else if (window.lokiFeatureFlags.useFileOnionRequests) {
|
||||
if (!srvPubKey) {
|
||||
throw new Error(
|
||||
'useFileOnionRequests=true but we do not have a server pubkey set.'
|
||||
);
|
||||
}
|
||||
mode = 'sendViaOnionOG';
|
||||
({ response, txtResponse, result } = await sendViaOnion(
|
||||
srvPubKey,
|
||||
|
@ -277,13 +282,9 @@ const serverRequest = async (endpoint, options = {}) => {
|
|||
options
|
||||
));
|
||||
} else {
|
||||
// disable check for .loki
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = host.match(/\.loki$/i)
|
||||
? '0'
|
||||
: '1';
|
||||
result = await nodeFetch(url, fetchOptions);
|
||||
// always make sure this check is enabled
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
|
||||
// we end up here only if window.lokiFeatureFlags.useFileOnionRequests is false
|
||||
log.info(`insecureNodeFetch => plaintext for ${url}`);
|
||||
result = await insecureNodeFetch(url, fetchOptions);
|
||||
|
||||
txtResponse = await result.text();
|
||||
// cloudflare timeouts (504s) will be html...
|
||||
|
@ -1395,23 +1396,13 @@ class LokiPublicChannelAPI {
|
|||
// do we already have this image? no, then
|
||||
|
||||
// download a copy and save it
|
||||
const imageData = await nodeFetch(avatarAbsUrl);
|
||||
// eslint-disable-next-line no-inner-declarations
|
||||
function toArrayBuffer(buf) {
|
||||
const ab = new ArrayBuffer(buf.length);
|
||||
const view = new Uint8Array(ab);
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let i = 0; i < buf.length; i++) {
|
||||
view[i] = buf[i];
|
||||
}
|
||||
return ab;
|
||||
}
|
||||
// eslint-enable-next-line no-inner-declarations
|
||||
const imageData = await this.serverAPI.downloadAttachment(
|
||||
avatarAbsUrl
|
||||
);
|
||||
|
||||
const buffer = await imageData.buffer();
|
||||
const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar(
|
||||
this.conversation.attributes,
|
||||
toArrayBuffer(buffer),
|
||||
imageData,
|
||||
{
|
||||
writeNewAttachmentData,
|
||||
deleteAttachmentData,
|
||||
|
|
|
@ -41,9 +41,14 @@ async function allowOnlyOneAtATime(name, process, timeout) {
|
|||
try {
|
||||
innerRetVal = await process();
|
||||
} catch (e) {
|
||||
log.error(
|
||||
`loki_primitives:::allowOnlyOneAtATime - error ${e.code} ${e.message}`
|
||||
);
|
||||
if (typeof e === 'string') {
|
||||
log.error(`loki_primitives:::allowOnlyOneAtATime - error ${e}`);
|
||||
} else {
|
||||
log.error(
|
||||
`loki_primitives:::allowOnlyOneAtATime - error ${e.code} ${e.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// clear timeout timer
|
||||
if (timeout) {
|
||||
if (timeoutTimer !== null) {
|
||||
|
|
|
@ -2,78 +2,93 @@
|
|||
const EventEmitter = require('events');
|
||||
const LokiAppDotNetAPI = require('./loki_app_dot_net_api');
|
||||
|
||||
const nodeFetch = require('node-fetch');
|
||||
const insecureNodeFetch = require('node-fetch');
|
||||
|
||||
/**
|
||||
* Tries to establish a connection with the specified open group url.
|
||||
*
|
||||
* This will try to do an onion routing call if the `useFileOnionRequests` feature flag is set,
|
||||
* or call directly insecureNodeFetch if it's not.
|
||||
*
|
||||
* Returns
|
||||
* * true if useFileOnionRequests is false and no exception where thrown by insecureNodeFetch
|
||||
* * true if useFileOnionRequests is true and we established a connection to the server with onion routing
|
||||
* * false otherwise
|
||||
*
|
||||
*/
|
||||
const validOpenGroupServer = async serverUrl => {
|
||||
// test to make sure it's online (and maybe has a valid SSL cert)
|
||||
try {
|
||||
const url = new URL(serverUrl);
|
||||
|
||||
if (window.lokiFeatureFlags.useFileOnionRequests) {
|
||||
// check for LSRPC
|
||||
if (!window.lokiFeatureFlags.useFileOnionRequests) {
|
||||
// we are not running with onion request
|
||||
// this is an insecure insecureNodeFetch. It will expose the user ip to the serverUrl (not onion routed)
|
||||
log.info(`insecureNodeFetch => plaintext for ${url.toString()}`);
|
||||
|
||||
// this is safe (as long as node's in your trust model)
|
||||
// because
|
||||
const result = await window.tokenlessFileServerAdnAPI.serverRequest(
|
||||
`loki/v1/getOpenGroupKey/${url.hostname}`
|
||||
);
|
||||
|
||||
if (result.response.meta.code === 200) {
|
||||
// supports it
|
||||
const obj = JSON.parse(result.response.data);
|
||||
const pubKey = dcodeIO.ByteBuffer.wrap(
|
||||
obj.data,
|
||||
'base64'
|
||||
).toArrayBuffer();
|
||||
// verify it works...
|
||||
// get around the FILESERVER_HOSTS filter by not using serverRequest
|
||||
const res = await LokiAppDotNetAPI.sendViaOnion(
|
||||
pubKey,
|
||||
url,
|
||||
{ method: 'GET' },
|
||||
{ noJson: true }
|
||||
);
|
||||
if (res.result && res.result.status === 200) {
|
||||
log.info(
|
||||
`loki_public_chat::validOpenGroupServer - onion routing enabled on ${url.toString()}`
|
||||
);
|
||||
// save pubkey for use...
|
||||
window.lokiPublicChatAPI.openGroupPubKeys[serverUrl] = pubKey;
|
||||
return true;
|
||||
}
|
||||
// otherwise fall back
|
||||
} else if (result.response.meta.code !== 404) {
|
||||
// unknown error code
|
||||
log.warn(
|
||||
'loki_public_chat::validOpenGroupServer - unknown error code',
|
||||
result.response.meta
|
||||
);
|
||||
}
|
||||
// we probably have to check the response here
|
||||
await insecureNodeFetch(serverUrl);
|
||||
return true;
|
||||
}
|
||||
// doesn't support it, fallback
|
||||
log.info(
|
||||
`loki_public_chat::validOpenGroupServer - directly contacting ${url.toString()}`
|
||||
// This MUST be an onion routing call, no nodeFetch calls below here.
|
||||
|
||||
/**
|
||||
* this is safe (as long as node's in your trust model)
|
||||
*
|
||||
* First, we need to fetch the open group public key of this open group.
|
||||
* The fileserver have all the open groups public keys.
|
||||
* We need the open group public key because for onion routing we will need to encode
|
||||
* our request with it.
|
||||
* We can just ask the file-server to get the one for the open group we are trying to add.
|
||||
*/
|
||||
|
||||
const result = await window.tokenlessFileServerAdnAPI.serverRequest(
|
||||
`loki/v1/getOpenGroupKey/${url.hostname}`
|
||||
);
|
||||
|
||||
// allow .loki (may only need an agent but not sure
|
||||
// until we have a .loki to test with)
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = url.host.match(/\.loki$/i)
|
||||
? '0'
|
||||
: '1';
|
||||
await nodeFetch(serverUrl);
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
|
||||
// const txt = await res.text();
|
||||
if (result.response.meta.code === 200) {
|
||||
// we got the public key of the server we are trying to add.
|
||||
// decode it.
|
||||
const obj = JSON.parse(result.response.data);
|
||||
const pubKey = dcodeIO.ByteBuffer.wrap(
|
||||
obj.data,
|
||||
'base64'
|
||||
).toArrayBuffer();
|
||||
// verify we can make an onion routed call to that open group with the decoded public key
|
||||
// get around the FILESERVER_HOSTS filter by not using serverRequest
|
||||
const res = await LokiAppDotNetAPI.sendViaOnion(
|
||||
pubKey,
|
||||
url,
|
||||
{ method: 'GET' },
|
||||
{ noJson: true }
|
||||
);
|
||||
if (res.result && res.result.status === 200) {
|
||||
log.info(
|
||||
`loki_public_chat::validOpenGroupServer - onion routing enabled on ${url.toString()}`
|
||||
);
|
||||
// save pubkey for use...
|
||||
window.lokiPublicChatAPI.openGroupPubKeys[serverUrl] = pubKey;
|
||||
return true;
|
||||
}
|
||||
// return here, just so we are sure adding some code below won't do a nodeFetch fallback
|
||||
return false;
|
||||
} else if (result.response.meta.code !== 404) {
|
||||
// unknown error code
|
||||
log.warn(
|
||||
'loki_public_chat::validOpenGroupServer - unknown error code',
|
||||
result.response.meta
|
||||
);
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
|
||||
log.warn(
|
||||
`loki_public_chat::validOpenGroupServer - failing to create ${serverUrl}`,
|
||||
e.code,
|
||||
e.message
|
||||
);
|
||||
// bail out if not valid enough
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
class LokiPublicChatFactoryAPI extends EventEmitter {
|
||||
|
@ -139,11 +154,11 @@ class LokiPublicChatFactoryAPI extends EventEmitter {
|
|||
);
|
||||
return null;
|
||||
}
|
||||
if (window.isDev) {
|
||||
log.info(
|
||||
`loki_public_chat::findOrCreateServer - set token ${thisServer.token} for ${serverUrl}`
|
||||
);
|
||||
}
|
||||
// if (window.isDev) {
|
||||
// log.info(
|
||||
// `loki_public_chat::findOrCreateServer - set token ${thisServer.token} for ${serverUrl}`
|
||||
// );
|
||||
// }
|
||||
|
||||
this.servers.push(thisServer);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ const { escapeRegExp } = require('lodash');
|
|||
|
||||
const APP_ROOT_PATH = path.join(__dirname, '..', '..', '..');
|
||||
const SESSION_ID_PATTERN = /\b(05[0-9a-f]{64})\b/gi;
|
||||
const SNODE_PATTERN = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
const GROUP_ID_PATTERN = /(group\()([^)]+)(\))/g;
|
||||
const REDACTION_PLACEHOLDER = '[REDACTED]';
|
||||
|
||||
|
@ -64,6 +65,14 @@ exports.redactSessionID = text => {
|
|||
return text.replace(SESSION_ID_PATTERN, REDACTION_PLACEHOLDER);
|
||||
};
|
||||
|
||||
exports.redactSnodeIP = text => {
|
||||
if (!is.string(text)) {
|
||||
throw new TypeError("'text' must be a string");
|
||||
}
|
||||
|
||||
return text.replace(SNODE_PATTERN, REDACTION_PLACEHOLDER);
|
||||
};
|
||||
|
||||
// redactGroupIds :: String -> String
|
||||
exports.redactGroupIds = text => {
|
||||
if (!is.string(text)) {
|
||||
|
@ -84,7 +93,8 @@ exports.redactSensitivePaths = exports._redactPath(APP_ROOT_PATH);
|
|||
exports.redactAll = compose(
|
||||
exports.redactSensitivePaths,
|
||||
exports.redactGroupIds,
|
||||
exports.redactSessionID
|
||||
exports.redactSessionID,
|
||||
exports.redactSnodeIP
|
||||
);
|
||||
|
||||
const removeNewlines = text => text.replace(/\r?\n|\r/g, '');
|
||||
|
|
|
@ -214,7 +214,6 @@ exports.deleteData = deleteOnDisk => {
|
|||
exports.isVoiceMessage = AttachmentTS.isVoiceMessage;
|
||||
exports.save = AttachmentTS.save;
|
||||
exports.getFileExtension = AttachmentTS.getFileExtension;
|
||||
exports.getSuggestedFilenameSending = AttachmentTS.getSuggestedFilenameSending;
|
||||
exports.arrayBufferFromFile = AttachmentTS.arrayBufferFromFile;
|
||||
|
||||
const THUMBNAIL_SIZE = 150;
|
||||
|
|
10
main.js
10
main.js
|
@ -492,20 +492,16 @@ setTimeout(readyForUpdates, TEN_MINUTES);
|
|||
|
||||
function openReleaseNotes() {
|
||||
shell.openExternal(
|
||||
`https://github.com/loki-project/session-desktop/releases/tag/v${app.getVersion()}`
|
||||
`https://github.com/oxen-io/session-desktop/releases/tag/v${app.getVersion()}`
|
||||
);
|
||||
}
|
||||
|
||||
function openNewBugForm() {
|
||||
shell.openExternal(
|
||||
'https://github.com/loki-project/session-desktop/issues/new'
|
||||
);
|
||||
shell.openExternal('https://github.com/oxen-io/session-desktop/issues/new');
|
||||
}
|
||||
|
||||
function openSupportPage() {
|
||||
shell.openExternal(
|
||||
'https://docs.loki.network/LokiServices/Messenger/Session/'
|
||||
);
|
||||
shell.openExternal('https://docs.oxen.io/products-built-on-oxen/session');
|
||||
}
|
||||
|
||||
function setupWithImport() {
|
||||
|
|
|
@ -305,6 +305,7 @@
|
|||
"debug_log_preload.js",
|
||||
"password_preload.js",
|
||||
"main.js",
|
||||
"certificates/**",
|
||||
"images/**",
|
||||
"fonts/*",
|
||||
"build/assets",
|
||||
|
|
16
preload.js
16
preload.js
|
@ -386,21 +386,25 @@ window.seedNodeList = JSON.parse(config.seedNodeList);
|
|||
|
||||
const { OnionPaths } = require('./ts/session/onions');
|
||||
|
||||
const { locale } = config;
|
||||
window.i18n = i18n.setup(locale, localeMessages);
|
||||
// moment does not support es-419 correctly (and cause white screen on app start)
|
||||
const localeForMoment = locale === 'es-419' ? 'es' : locale;
|
||||
const { locale: localFromEnv } = config;
|
||||
window.i18n = i18n.setup(localFromEnv, localeMessages);
|
||||
|
||||
// moment does not support es-419 correctly (and cause white screen on app start)
|
||||
window.moment = require('moment');
|
||||
|
||||
window.moment.updateLocale(localeForMoment, {
|
||||
// Default to the locale from env. It will be overriden if moment
|
||||
// does not recognize it with what moment knows which is the closest.
|
||||
// i.e. es-419 will return 'es'.
|
||||
// We just need to use what we got from moment on the updateLocale below
|
||||
|
||||
const localeSetForMoment = window.moment.locale(localFromEnv);
|
||||
window.moment.updateLocale(localeSetForMoment, {
|
||||
relativeTime: {
|
||||
s: window.i18n('timestamp_s'),
|
||||
m: window.i18n('timestamp_m'),
|
||||
h: window.i18n('timestamp_h'),
|
||||
},
|
||||
});
|
||||
window.moment.locale(localeForMoment);
|
||||
|
||||
window.OnionPaths = OnionPaths;
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ type PropsHousekeeping = {
|
|||
onUnblockContact?: () => void;
|
||||
onInviteContacts?: () => void;
|
||||
onClearNickname?: () => void;
|
||||
onMarkAllRead: () => void;
|
||||
theme: DefaultTheme;
|
||||
};
|
||||
|
||||
|
@ -62,7 +63,6 @@ class ConversationListItem extends React.PureComponent<Props> {
|
|||
public renderAvatar() {
|
||||
const {
|
||||
avatarPath,
|
||||
i18n,
|
||||
name,
|
||||
phoneNumber,
|
||||
profileName,
|
||||
|
|
|
@ -83,6 +83,8 @@ interface Props {
|
|||
onAvatarClick?: (userPubKey: string) => void;
|
||||
onUpdateGroupName: () => void;
|
||||
|
||||
onMarkAllRead: () => void;
|
||||
|
||||
memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
|
||||
theme: DefaultTheme;
|
||||
}
|
||||
|
|
|
@ -102,11 +102,14 @@ const Section = (props: { type: SectionType; avatarPath?: string }) => {
|
|||
iconType = SessionIconType.Moon;
|
||||
}
|
||||
|
||||
const unreadToShow =
|
||||
type === SectionType.Message ? unreadMessageCount : undefined;
|
||||
|
||||
return (
|
||||
<SessionIconButton
|
||||
iconSize={SessionIconSize.Medium}
|
||||
iconType={iconType}
|
||||
notificationCount={unreadMessageCount}
|
||||
notificationCount={unreadToShow}
|
||||
onClick={handleClick}
|
||||
isSelected={isSelected}
|
||||
theme={theme}
|
||||
|
|
|
@ -127,8 +127,7 @@ export class LeftPaneContactSection extends React.Component<Props, State> {
|
|||
if (error) {
|
||||
ToastUtils.pushToastError('addContact', error);
|
||||
} else {
|
||||
// tslint:disable-next-line: no-floating-promises
|
||||
ConversationController.getInstance()
|
||||
void ConversationController.getInstance()
|
||||
.getOrCreateAndWait(sessionID, 'private')
|
||||
.then(() => {
|
||||
this.props.openConversationExternal(sessionID);
|
||||
|
|
|
@ -434,6 +434,10 @@ export class SessionConversation extends React.Component<Props, State> {
|
|||
window.Whisper.events.trigger('inviteContacts', conversation);
|
||||
},
|
||||
|
||||
onMarkAllRead: () => {
|
||||
void conversation.markReadBouncy(Date.now());
|
||||
},
|
||||
|
||||
onAddModerators: () => {
|
||||
window.Whisper.events.trigger('addModerators', conversation);
|
||||
},
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { arrayBufferFromFile } from '../../../types/Attachment';
|
||||
import { AttachmentUtil, LinkPreviewUtil } from '../../../util';
|
||||
import { StagedLinkPreviewData } from './SessionCompositionBox';
|
||||
import fetch from 'node-fetch';
|
||||
import { default as insecureNodeFetch } from 'node-fetch';
|
||||
import { fetchLinkPreviewImage } from '../../../util/linkPreviewFetch';
|
||||
import { AbortSignal } from 'abort-controller';
|
||||
import { StagedLinkPreview } from '../../conversation/StagedLinkPreview';
|
||||
|
@ -37,8 +37,10 @@ export const getPreview = async (
|
|||
throw new Error('Link not safe for preview');
|
||||
}
|
||||
|
||||
window.log.info('insecureNodeFetch => plaintext for getPreview()');
|
||||
|
||||
const linkPreviewMetadata = await LinkPreviewUtil.fetchLinkPreviewMetadata(
|
||||
fetch,
|
||||
insecureNodeFetch,
|
||||
url,
|
||||
abortSignal
|
||||
);
|
||||
|
@ -51,8 +53,10 @@ export const getPreview = async (
|
|||
if (imageHref && window.Signal.LinkPreviews.isLinkSafeToPreview(imageHref)) {
|
||||
let objectUrl: void | string;
|
||||
try {
|
||||
window.log.info('insecureNodeFetch => plaintext for getPreview()');
|
||||
|
||||
const fullSizeImage = await fetchLinkPreviewImage(
|
||||
fetch,
|
||||
insecureNodeFetch,
|
||||
imageHref,
|
||||
abortSignal
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
getDisappearingMenuItem,
|
||||
getInviteContactMenuItem,
|
||||
getLeaveGroupMenuItem,
|
||||
getMarkAllReadMenuItem,
|
||||
getRemoveModeratorsMenuItem,
|
||||
getUpdateGroupNameMenuItem,
|
||||
} from './Menu';
|
||||
|
@ -31,6 +32,7 @@ export type PropsConversationHeaderMenu = {
|
|||
onInviteContacts?: () => void;
|
||||
|
||||
onLeaveGroup: () => void;
|
||||
onMarkAllRead: () => void;
|
||||
onAddModerators: () => void;
|
||||
onRemoveModerators: () => void;
|
||||
onUpdateGroupName: () => void;
|
||||
|
@ -55,6 +57,7 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
|
|||
onDeleteMessages,
|
||||
onDeleteContact,
|
||||
onCopyPublicKey,
|
||||
onMarkAllRead,
|
||||
onLeaveGroup,
|
||||
onAddModerators,
|
||||
onRemoveModerators,
|
||||
|
@ -86,6 +89,7 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
|
|||
)}
|
||||
|
||||
{getCopyMenuItem(isPublic, isGroup, onCopyPublicKey, window.i18n)}
|
||||
{getMarkAllReadMenuItem(onMarkAllRead, window.i18n)}
|
||||
{getDeleteMessagesMenuItem(isPublic, onDeleteMessages, window.i18n)}
|
||||
{getAddModeratorsMenuItem(
|
||||
isAdmin,
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
getDeleteMessagesMenuItem,
|
||||
getInviteContactMenuItem,
|
||||
getLeaveGroupMenuItem,
|
||||
getMarkAllReadMenuItem,
|
||||
} from './Menu';
|
||||
|
||||
export type PropsContextConversationItem = {
|
||||
|
@ -25,6 +26,7 @@ export type PropsContextConversationItem = {
|
|||
onDeleteContact?: () => void;
|
||||
onLeaveGroup?: () => void;
|
||||
onBlockContact?: () => void;
|
||||
onMarkAllRead: () => void;
|
||||
onCopyPublicKey?: () => void;
|
||||
onUnblockContact?: () => void;
|
||||
onInviteContacts?: () => void;
|
||||
|
@ -48,6 +50,7 @@ export const ConversationListItemContextMenu = (
|
|||
onBlockContact,
|
||||
onClearNickname,
|
||||
onCopyPublicKey,
|
||||
onMarkAllRead,
|
||||
onUnblockContact,
|
||||
onInviteContacts,
|
||||
onLeaveGroup,
|
||||
|
@ -81,6 +84,8 @@ export const ConversationListItemContextMenu = (
|
|||
onCopyPublicKey,
|
||||
window.i18n
|
||||
)}
|
||||
{getMarkAllReadMenuItem(onMarkAllRead, window.i18n)}
|
||||
|
||||
{getDeleteMessagesMenuItem(isPublic, onDeleteMessages, window.i18n)}
|
||||
{getInviteContactMenuItem(
|
||||
type === 'group',
|
||||
|
|
|
@ -32,8 +32,9 @@ function showDeleteMessages(isPublic: boolean): boolean {
|
|||
return !isPublic;
|
||||
}
|
||||
|
||||
// we want to show the copyId for open groups and private chats only
|
||||
function showCopyId(isPublic: boolean, isGroup: boolean): boolean {
|
||||
return !isGroup; // || isPublic;
|
||||
return !isGroup || isPublic;
|
||||
}
|
||||
|
||||
function showDeleteContact(
|
||||
|
@ -196,12 +197,21 @@ export function getCopyMenuItem(
|
|||
i18n: LocalizerType
|
||||
): JSX.Element | null {
|
||||
if (showCopyId(Boolean(isPublic), Boolean(isGroup))) {
|
||||
const copyIdLabel = i18n('copySessionID');
|
||||
const copyIdLabel = isPublic
|
||||
? i18n('copyOpenGroupURL')
|
||||
: i18n('copySessionID');
|
||||
return <Item onClick={action}>{copyIdLabel}</Item>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getMarkAllReadMenuItem(
|
||||
action: any,
|
||||
i18n: LocalizerType
|
||||
): JSX.Element | null {
|
||||
return <Item onClick={action}>{i18n('markAllAsRead')}</Item>;
|
||||
}
|
||||
|
||||
export function getDisappearingMenuItem(
|
||||
isPublic: boolean | undefined,
|
||||
isKickedFromGroup: boolean | undefined,
|
||||
|
|
|
@ -423,6 +423,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
onInviteContacts: () => {
|
||||
window.Whisper.events.trigger('inviteContacts', this);
|
||||
},
|
||||
onMarkAllRead: () => {
|
||||
void this.markReadBouncy(Date.now());
|
||||
},
|
||||
onClearNickname: () => {
|
||||
void this.setLokiProfile({ displayName: null });
|
||||
},
|
||||
|
@ -434,9 +437,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
const newAdmins = _.sortBy(groupAdmins);
|
||||
|
||||
if (_.isEqual(existingAdmins, newAdmins)) {
|
||||
window.log.info(
|
||||
'Skipping updates of groupAdmins/moderators. No change detected.'
|
||||
);
|
||||
// window.log.info(
|
||||
// 'Skipping updates of groupAdmins/moderators. No change detected.'
|
||||
// );
|
||||
return;
|
||||
}
|
||||
this.set({ groupAdmins });
|
||||
|
@ -468,7 +471,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
}
|
||||
|
||||
public async getUnreadCount() {
|
||||
window.log.warn('getUnreadCount is slow');
|
||||
const unreadCount = await getUnreadCountByConversation(this.id);
|
||||
|
||||
return unreadCount;
|
||||
|
@ -1351,6 +1353,14 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
}
|
||||
|
||||
public copyPublicKey() {
|
||||
if (this.isPublic()) {
|
||||
const atIndex = this.id.indexOf('@') as number;
|
||||
const openGroupUrl = this.id.substr(atIndex + 1);
|
||||
window.clipboard.writeText(openGroupUrl);
|
||||
|
||||
ToastUtils.pushCopiedToClipBoard();
|
||||
return;
|
||||
}
|
||||
window.clipboard.writeText(this.id);
|
||||
|
||||
ToastUtils.pushCopiedToClipBoard();
|
||||
|
|
|
@ -2,8 +2,9 @@ import { allowOnlyOneAtATime } from '../../../js/modules/loki_primitives';
|
|||
import { getGuardNodes } from '../../../ts/data/data';
|
||||
import * as SnodePool from '../snode_api/snodePool';
|
||||
import _ from 'lodash';
|
||||
import fetch from 'node-fetch';
|
||||
import { default as insecureNodeFetch } from 'node-fetch';
|
||||
import { UserUtils } from '../utils';
|
||||
import { snodeHttpsAgent } from '../snode_api/onions';
|
||||
|
||||
type Snode = SnodePool.Snode;
|
||||
|
||||
|
@ -155,23 +156,22 @@ export class OnionPaths {
|
|||
body: JSON.stringify(body),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
timeout: 10000, // 10s, we want a smaller timeout for testing
|
||||
agent: snodeHttpsAgent,
|
||||
};
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
|
||||
let response;
|
||||
|
||||
try {
|
||||
// Log this line for testing
|
||||
// curl -k -X POST -H 'Content-Type: application/json' -d '"+fetchOptions.body.replace(/"/g, "\\'")+"'", url
|
||||
response = await fetch(url, fetchOptions);
|
||||
window.log.info('insecureNodeFetch => plaintext for testGuardNode');
|
||||
|
||||
response = await insecureNodeFetch(url, fetchOptions);
|
||||
} catch (e) {
|
||||
if (e.type === 'request-timeout') {
|
||||
log.warn('test timeout for node,', snode);
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
|
@ -312,6 +312,6 @@ export class OnionPaths {
|
|||
this.onionPaths.push({ path, bad: false });
|
||||
}
|
||||
|
||||
log.info(`Built ${this.onionPaths.length} onion paths`, this.onionPaths);
|
||||
log.info(`Built ${this.onionPaths.length} onion paths`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +1,15 @@
|
|||
import fetch from 'node-fetch';
|
||||
import https from 'https';
|
||||
import { default as insecureNodeFetch } from 'node-fetch';
|
||||
|
||||
import { Snode } from './snodePool';
|
||||
|
||||
import { lokiOnionFetch, SnodeResponse } from './onions';
|
||||
|
||||
const snodeHttpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
async function lokiPlainFetch(
|
||||
url: string,
|
||||
fetchOptions: any
|
||||
): Promise<boolean | SnodeResponse> {
|
||||
const { log } = window;
|
||||
|
||||
if (url.match(/https:\/\//)) {
|
||||
// import that this does not get set in lokiFetch fetchOptions
|
||||
fetchOptions.agent = snodeHttpsAgent;
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
} else {
|
||||
log.debug('lokirpc:::lokiFetch - http communication', url);
|
||||
}
|
||||
const response = await fetch(url, fetchOptions);
|
||||
// restore TLS checking
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
|
||||
|
||||
if (!response.ok) {
|
||||
throw new window.textsecure.HTTPError('Loki_rpc error', response);
|
||||
}
|
||||
const result = await response.text();
|
||||
|
||||
return {
|
||||
body: result,
|
||||
status: response.status,
|
||||
};
|
||||
}
|
||||
import { lokiOnionFetch, snodeHttpsAgent, SnodeResponse } from './onions';
|
||||
|
||||
interface FetchOptions {
|
||||
method: string;
|
||||
}
|
||||
|
||||
// A small wrapper around node-fetch which deserializes response
|
||||
// returns nodeFetch response or false
|
||||
// returns insecureNodeFetch response or false
|
||||
async function lokiFetch(
|
||||
url: string,
|
||||
options: FetchOptions,
|
||||
|
@ -64,7 +31,23 @@ async function lokiFetch(
|
|||
return await lokiOnionFetch(fetchOptions.body, targetNode);
|
||||
}
|
||||
|
||||
return await lokiPlainFetch(url, fetchOptions);
|
||||
if (url.match(/https:\/\//)) {
|
||||
// import that this does not get set in lokiFetch fetchOptions
|
||||
fetchOptions.agent = snodeHttpsAgent;
|
||||
}
|
||||
window.log.warn(`insecureNodeFetch => lokiFetch of ${url}`);
|
||||
|
||||
const response = await insecureNodeFetch(url, fetchOptions);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new window.textsecure.HTTPError('Loki_rpc error', response);
|
||||
}
|
||||
const result = await response.text();
|
||||
|
||||
return {
|
||||
body: result,
|
||||
status: response.status,
|
||||
};
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOTFOUND') {
|
||||
throw new window.textsecure.NotFoundError('Failed to resolve address', e);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import fetch from 'node-fetch';
|
||||
import { default as insecureNodeFetch } from 'node-fetch';
|
||||
import https from 'https';
|
||||
|
||||
import { Snode } from './snodePool';
|
||||
|
@ -353,7 +353,7 @@ const processOnionResponse = async (
|
|||
}
|
||||
};
|
||||
|
||||
const snodeHttpsAgent = new https.Agent({
|
||||
export const snodeHttpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
|
@ -457,7 +457,10 @@ const sendOnionRequest = async (
|
|||
const target = useV2 ? '/onion_req/v2' : '/onion_req';
|
||||
|
||||
const guardUrl = `https://${nodePath[0].ip}:${nodePath[0].port}${target}`;
|
||||
const response = await fetch(guardUrl, guardFetchOptions);
|
||||
// no logs for that one as we do need to call insecureNodeFetch to our guardNode
|
||||
// window.log.info('insecureNodeFetch => plaintext for sendOnionRequest');
|
||||
|
||||
const response = await insecureNodeFetch(guardUrl, guardFetchOptions);
|
||||
|
||||
return processOnionResponse(
|
||||
reqIdx,
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
// we don't throw or catch here
|
||||
|
||||
import { default as insecureNodeFetch } from 'node-fetch';
|
||||
import https from 'https';
|
||||
import fetch from 'node-fetch';
|
||||
import crypto from 'crypto';
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import tls from 'tls';
|
||||
import Electron from 'electron';
|
||||
|
||||
const { remote } = Electron;
|
||||
|
||||
import { snodeRpc } from './lokiRpc';
|
||||
import { sendOnionRequestLsrpcDest, SnodeResponse } from './onions';
|
||||
import {
|
||||
sendOnionRequestLsrpcDest,
|
||||
snodeHttpsAgent,
|
||||
SnodeResponse,
|
||||
} from './onions';
|
||||
|
||||
import { sleepFor } from '../../../js/modules/loki_primitives';
|
||||
|
||||
|
@ -17,10 +28,10 @@ import {
|
|||
updateSnodesFor,
|
||||
} from './snodePool';
|
||||
|
||||
const snodeHttpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* Currently unused. If we need it again, be sure to update it to onion routing rather
|
||||
* than using a plain nodeFetch
|
||||
*/
|
||||
export async function getVersion(
|
||||
node: Snode,
|
||||
retries: number = 0
|
||||
|
@ -30,11 +41,13 @@ export async function getVersion(
|
|||
const { log } = window;
|
||||
|
||||
try {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
const result = await fetch(`https://${node.ip}:${node.port}/get_stats/v1`, {
|
||||
agent: snodeHttpsAgent,
|
||||
});
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
|
||||
window.log.warn('insecureNodeFetch => plaintext for getVersion');
|
||||
const result = await insecureNodeFetch(
|
||||
`https://${node.ip}:${node.port}/get_stats/v1`,
|
||||
{
|
||||
agent: snodeHttpsAgent,
|
||||
}
|
||||
);
|
||||
const data = await result.json();
|
||||
if (data.version) {
|
||||
return data.version;
|
||||
|
@ -74,6 +87,93 @@ export async function getVersion(
|
|||
}
|
||||
}
|
||||
|
||||
const sha256 = (s: string) => {
|
||||
return crypto
|
||||
.createHash('sha256')
|
||||
.update(s)
|
||||
.digest('base64');
|
||||
};
|
||||
|
||||
const getSslAgentForSeedNode = (seedNodeHost: string) => {
|
||||
let filePrefix = '';
|
||||
let pubkey256 = '';
|
||||
let cert256 = '';
|
||||
|
||||
switch (seedNodeHost) {
|
||||
case 'storage.seed1.loki.network':
|
||||
filePrefix = 'storage-seed-1';
|
||||
pubkey256 = 'JOsnIcAanVbgECNA8lHtC8f/cqN9m8EP7jKT6XCjeL8=';
|
||||
cert256 =
|
||||
'6E:2B:AC:F3:6E:C1:FF:FF:24:F3:CA:92:C6:94:81:B4:82:43:DF:C7:C6:03:98:B8:F5:6B:7D:30:7B:16:C1:CB';
|
||||
break;
|
||||
case 'storage.seed3.loki.network':
|
||||
filePrefix = 'storage-seed-3';
|
||||
pubkey256 = 'mMmZD3lG4Fi7nTC/EWzRVaU3bbCLsH6Ds2FHSTpo0Rk=';
|
||||
cert256 =
|
||||
'24:13:4C:0A:03:D8:42:A6:09:DE:35:76:F4:BD:FB:11:60:DB:F9:88:9F:98:46:B7:60:A6:60:0C:4C:CF:60:72';
|
||||
|
||||
break;
|
||||
case 'public.loki.foundation':
|
||||
filePrefix = 'public-loki-foundation';
|
||||
pubkey256 = 'W+Zv52qlcm1BbdpJzFwxZrE7kfmEboq7h3Dp/+Q3RPg=';
|
||||
cert256 =
|
||||
'40:E4:67:7D:18:6B:4D:08:8D:E9:D5:47:52:25:B8:28:E0:D3:63:99:9B:38:46:7D:92:19:5B:61:B9:AE:0E:EA';
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown seed node: ${seedNodeHost}`);
|
||||
}
|
||||
// tslint:disable: non-literal-fs-path
|
||||
// read the cert each time. We only run this request once for each seed node nevertheless.
|
||||
const appPath = remote.app.getAppPath();
|
||||
const crt = fs.readFileSync(
|
||||
path.join(appPath, `/certificates/${filePrefix}.crt`),
|
||||
'utf-8'
|
||||
);
|
||||
// debugger;
|
||||
const sslOptions = {
|
||||
// as the seed nodes are using a self signed certificate, we have to provide it here.
|
||||
ca: crt,
|
||||
// we might need to selectively disable that for tests on swarm-testing or so.
|
||||
// we have to reject them, otherwise our errors returned in the checkServerIdentity are simply not making the call fail.
|
||||
// so in production, rejectUnauthorized must be true.
|
||||
rejectUnauthorized: true,
|
||||
keepAlive: false,
|
||||
checkServerIdentity: (host: string, cert: any) => {
|
||||
// Make sure the certificate is issued to the host we are connected to
|
||||
const err = tls.checkServerIdentity(host, cert);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// we might need to selectively disable that for tests on swarm-testing or so.
|
||||
|
||||
// Pin the public key, similar to HPKP pin-sha25 pinning
|
||||
if (sha256(cert.pubkey) !== pubkey256) {
|
||||
const msg =
|
||||
'Certificate verification error: ' +
|
||||
`The public key of '${cert.subject.CN}' ` +
|
||||
'does not match our pinned fingerprint';
|
||||
return new Error(msg);
|
||||
}
|
||||
|
||||
// Pin the exact certificate, rather than the pub key
|
||||
if (cert.fingerprint256 !== cert256) {
|
||||
const msg =
|
||||
'Certificate verification error: ' +
|
||||
`The certificate of '${cert.subject.CN}' ` +
|
||||
'does not match our pinned fingerprint';
|
||||
return new Error(msg);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
|
||||
// we're creating a new Agent that will now use the certs we have configured
|
||||
return new https.Agent(sslOptions);
|
||||
};
|
||||
|
||||
export async function getSnodesFromSeedUrl(urlObj: URL): Promise<Array<any>> {
|
||||
const { log } = window;
|
||||
|
||||
|
@ -99,14 +199,18 @@ export async function getSnodesFromSeedUrl(urlObj: URL): Promise<Array<any>> {
|
|||
method: 'get_n_service_nodes',
|
||||
params,
|
||||
};
|
||||
const sslAgent = getSslAgentForSeedNode(urlObj.hostname);
|
||||
|
||||
const fetchOptions = {
|
||||
method: 'POST',
|
||||
timeout: 10000,
|
||||
body: JSON.stringify(body),
|
||||
};
|
||||
|
||||
const response = await fetch(url, fetchOptions);
|
||||
agent: sslAgent,
|
||||
};
|
||||
window.log.info('insecureNodeFetch => plaintext for getSnodesFromSeedUrl');
|
||||
|
||||
const response = await insecureNodeFetch(url, fetchOptions);
|
||||
|
||||
if (response.status !== 200) {
|
||||
log.error(
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
import semver from 'semver';
|
||||
import _ from 'lodash';
|
||||
|
||||
import {
|
||||
abortableIterator,
|
||||
allowOnlyOneAtATime,
|
||||
} from '../../../js/modules/loki_primitives';
|
||||
|
||||
import {
|
||||
getSnodesFromSeedUrl,
|
||||
getVersion,
|
||||
requestSnodesForPubkey,
|
||||
} from './serviceNodeAPI';
|
||||
import { getSnodesFromSeedUrl, requestSnodesForPubkey } from './serviceNodeAPI';
|
||||
|
||||
import {
|
||||
getSwarmNodesForPubkey,
|
||||
updateSwarmNodesForPubkey,
|
||||
} from '../../../ts/data/data';
|
||||
|
||||
import semver from 'semver';
|
||||
import _ from 'lodash';
|
||||
|
||||
export type SnodeEdKey = string;
|
||||
|
||||
const MIN_NODES = 3;
|
||||
|
@ -60,6 +56,7 @@ async function tryGetSnodeListFromLokidSeednode(
|
|||
let snodes = [];
|
||||
try {
|
||||
const tryUrl = new URL(seedNode.url);
|
||||
|
||||
snodes = await getSnodesFromSeedUrl(tryUrl);
|
||||
// throw before clearing the lock, so the retries can kick in
|
||||
if (snodes.length === 0) {
|
||||
|
@ -108,8 +105,7 @@ export function markNodeUnreachable(snode: Snode): void {
|
|||
for (const [pubkey, nodes] of nodesForPubkey) {
|
||||
const edkeys = _.filter(nodes, edkey => edkey !== snode.pubkey_ed25519);
|
||||
|
||||
// tslint:disable-next-line no-floating-promises
|
||||
internalUpdateSnodesFor(pubkey, edkeys);
|
||||
void internalUpdateSnodesFor(pubkey, edkeys);
|
||||
}
|
||||
|
||||
log.warn(
|
||||
|
@ -137,13 +133,18 @@ function compareSnodes(lhs: any, rhs: any): boolean {
|
|||
return lhs.pubkey_ed25519 === rhs.pubkey_ed25519;
|
||||
}
|
||||
|
||||
// WARNING: this leaks our IP to all snodes but with no other identifying information
|
||||
// except "that a client started up" or "ran out of random pool snodes"
|
||||
// and the order of the list is randomized, so a snode can't tell if it just started or not
|
||||
/**
|
||||
* Request the version of the snode.
|
||||
* THIS IS AN INSECURE NODE FETCH and leaks our IP to all snodes but with no other identifying information
|
||||
* except "that a client started up" or "ran out of random pool snodes"
|
||||
* and the order of the list is randomized, so a snode can't tell if it just started or not
|
||||
*/
|
||||
async function requestVersion(node: any): Promise<void> {
|
||||
const { log } = window;
|
||||
|
||||
const result = await getVersion(node);
|
||||
// WARNING: getVersion is doing an insecure node fetch.
|
||||
// be sure to update getVersion to onion routing if we need this call again.
|
||||
const result = false; // await getVersion(node);
|
||||
|
||||
if (result === false) {
|
||||
return;
|
||||
|
@ -178,9 +179,14 @@ export function getNodesMinVersion(minVersion: string): Array<Snode> {
|
|||
);
|
||||
}
|
||||
|
||||
// now get version for all snodes
|
||||
// also acts an early online test/purge of bad nodes
|
||||
export async function getAllVerionsForRandomSnodePool(): Promise<void> {
|
||||
/**
|
||||
* Currently unused as it makes call over insecure node fetch and we don't need
|
||||
* to filter out nodes by versions anymore.
|
||||
*
|
||||
* now get version for all snodes
|
||||
* also acts an early online test/purge of bad nodes
|
||||
*/
|
||||
export async function getAllVersionsForRandomSnodePool(): Promise<void> {
|
||||
const { log } = window;
|
||||
|
||||
// let count = 0;
|
||||
|
@ -192,7 +198,7 @@ export async function getAllVerionsForRandomSnodePool(): Promise<void> {
|
|||
await requestVersion(node);
|
||||
} catch (e) {
|
||||
log.error(
|
||||
'LokiSnodeAPI::_getAllVerionsForRandomSnodePool - error',
|
||||
'LokiSnodeAPI::_getAllVersionsForRandomSnodePool - error',
|
||||
e.code,
|
||||
e.message
|
||||
);
|
||||
|
@ -211,7 +217,7 @@ export async function getAllVerionsForRandomSnodePool(): Promise<void> {
|
|||
return curVal;
|
||||
}, []);
|
||||
log.debug(
|
||||
`LokiSnodeAPI::_getAllVerionsForRandomSnodePool - ${versions.length} versions retrieved from network!:`,
|
||||
`LokiSnodeAPI::_getAllVersionsForRandomSnodePool - ${versions.length} versions retrieved from network!:`,
|
||||
versions.join(',')
|
||||
);
|
||||
}
|
||||
|
@ -248,8 +254,7 @@ async function getSnodeListFromLokidSeednode(
|
|||
'seed nodes total',
|
||||
seedNodes.length
|
||||
);
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
getSnodeListFromLokidSeednode(seedNodes, retries + 1);
|
||||
void getSnodeListFromLokidSeednode(seedNodes, retries + 1);
|
||||
}, retries * retries * 5000);
|
||||
} else {
|
||||
log.error('loki_snode_api::getSnodeListFromLokidSeednode - failing');
|
||||
|
@ -262,7 +267,7 @@ async function getSnodeListFromLokidSeednode(
|
|||
async function refreshRandomPoolDetail(seedNodes: Array<any>): Promise<void> {
|
||||
const { log } = window;
|
||||
|
||||
// are we running any _getAllVerionsForRandomSnodePool
|
||||
// are we running any _getAllVersionsForRandomSnodePool
|
||||
if (stopGetAllVersionPromiseControl !== false) {
|
||||
// we are, stop them
|
||||
stopGetAllVersionPromiseControl();
|
||||
|
@ -286,8 +291,9 @@ async function refreshRandomPoolDetail(seedNodes: Array<any>): Promise<void> {
|
|||
randomSnodePool.length,
|
||||
'snodes'
|
||||
);
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
getAllVerionsForRandomSnodePool();
|
||||
// Warning: the call below will call getVersions to all existing nodes.
|
||||
// And not with onion routing
|
||||
// void getAllVersionsForRandomSnodePool();
|
||||
} catch (e) {
|
||||
log.warn('LokiSnodeAPI::refreshRandomPool - error', e.code, e.message);
|
||||
/*
|
||||
|
@ -358,8 +364,7 @@ export async function getSnodesFor(pubkey: string): Promise<Array<Snode>> {
|
|||
const freshNodes = _.shuffle(await requestSnodesForPubkey(pubkey));
|
||||
|
||||
const edkeys = freshNodes.map((n: Snode) => n.pubkey_ed25519);
|
||||
// tslint:disable-next-line no-floating-promises
|
||||
internalUpdateSnodesFor(pubkey, edkeys);
|
||||
void internalUpdateSnodesFor(pubkey, edkeys);
|
||||
// TODO: We could probably check that the retuned sndoes are not "unreachable"
|
||||
|
||||
return freshNodes;
|
||||
|
|
|
@ -29,7 +29,6 @@ export function processMessage(message: string, options: any = {}) {
|
|||
const dataPlaintext = new Uint8Array(StringUtils.encode(message, 'base64'));
|
||||
const messageBuf = SignalService.WebSocketMessage.decode(dataPlaintext);
|
||||
if (messageBuf.type === SignalService.WebSocketMessage.Type.REQUEST) {
|
||||
// tslint:disable-next-line no-floating-promises
|
||||
Receiver.handleRequest(messageBuf.request?.body, options);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { processMessage, SwarmPolling } from './swarmPolling';
|
||||
import fetch from 'node-fetch';
|
||||
import { default as insecureNodeFetch } from 'node-fetch';
|
||||
import { PubKey } from '../types';
|
||||
|
||||
export class SwarmPollingStub extends SwarmPolling {
|
||||
|
@ -12,7 +12,8 @@ export class SwarmPollingStub extends SwarmPolling {
|
|||
method: 'GET',
|
||||
};
|
||||
|
||||
const res = await fetch(
|
||||
// insecureNodeFetch but this is a stub
|
||||
const res = await insecureNodeFetch(
|
||||
`${this.baseUrl}/messages?pubkey=${pubkeyStr}`,
|
||||
get
|
||||
);
|
||||
|
|
|
@ -87,15 +87,16 @@ export interface ConversationType {
|
|||
groupAdmins?: Array<string>; // admins for closed groups and moderators for open groups
|
||||
members?: Array<string>; // members for closed groups only
|
||||
|
||||
onClick?: () => any;
|
||||
onBlockContact?: () => any;
|
||||
onUnblockContact?: () => any;
|
||||
onCopyPublicKey?: () => any;
|
||||
onDeleteContact?: () => any;
|
||||
onLeaveGroup?: () => any;
|
||||
onDeleteMessages?: () => any;
|
||||
onInviteContacts?: () => any;
|
||||
onClearNickname?: () => any;
|
||||
onClick?: () => void;
|
||||
onBlockContact?: () => void;
|
||||
onUnblockContact?: () => void;
|
||||
onCopyPublicKey?: () => void;
|
||||
onDeleteContact?: () => void;
|
||||
onLeaveGroup?: () => void;
|
||||
onDeleteMessages?: () => void;
|
||||
onInviteContacts?: () => void;
|
||||
onMarkAllRead?: () => void;
|
||||
onClearNickname?: () => void;
|
||||
}
|
||||
|
||||
export type ConversationLookupType = {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { StringUtils } from '../../../../session/utils';
|
||||
|
||||
import fetch from 'node-fetch';
|
||||
import { default as insecureNodeFetch } from 'node-fetch';
|
||||
|
||||
class StubMessageAPI {
|
||||
public ourKey: string;
|
||||
|
@ -23,7 +23,9 @@ class StubMessageAPI {
|
|||
};
|
||||
|
||||
const data64 = StringUtils.decode(data, 'base64');
|
||||
await fetch(
|
||||
// insecureNodeFetch but this is a stub
|
||||
|
||||
await insecureNodeFetch(
|
||||
`${
|
||||
this.baseUrl
|
||||
}/messages?pubkey=${pubKey}×tamp=${messageTimeStamp}&data=${encodeURIComponent(
|
||||
|
|
|
@ -54,6 +54,46 @@ describe('Attachment', () => {
|
|||
const expected = 'session-attachment_003.mov';
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
it('should generate a filename with an extension if contentType is not setup', () => {
|
||||
const attachment: Attachment.AttachmentType = {
|
||||
fileName: 'funny-cat.ini',
|
||||
url: 'funny-cat.ini',
|
||||
contentType: '',
|
||||
};
|
||||
const actual = Attachment.getSuggestedFilename({
|
||||
attachment,
|
||||
index: 3,
|
||||
});
|
||||
const expected = 'session-attachment_003.ini';
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('should generate a filename with an extension if contentType is text/plain', () => {
|
||||
const attachment: Attachment.AttachmentType = {
|
||||
fileName: 'funny-cat.txt',
|
||||
url: 'funny-cat.txt',
|
||||
contentType: 'text/plain',
|
||||
};
|
||||
const actual = Attachment.getSuggestedFilename({
|
||||
attachment,
|
||||
index: 3,
|
||||
});
|
||||
const expected = 'session-attachment_003.txt';
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
it('should generate a filename with an extension if contentType is json', () => {
|
||||
const attachment: Attachment.AttachmentType = {
|
||||
fileName: 'funny-cat.json',
|
||||
url: 'funny-cat.json',
|
||||
contentType: '',
|
||||
};
|
||||
const actual = Attachment.getSuggestedFilename({
|
||||
attachment,
|
||||
index: 3,
|
||||
});
|
||||
const expected = 'session-attachment_003.json';
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
});
|
||||
context('for attachment without filename', () => {
|
||||
it('should generate a filename based on timestamp', () => {
|
||||
|
|
|
@ -396,8 +396,17 @@ export const getSuggestedFilenameSending = ({
|
|||
export const getFileExtension = (
|
||||
attachment: AttachmentType
|
||||
): string | undefined => {
|
||||
if (!attachment.contentType) {
|
||||
return;
|
||||
// we override textplain to the extension of the file
|
||||
if (!attachment.contentType || attachment.contentType === 'text/plain') {
|
||||
if (attachment.fileName?.length) {
|
||||
const dotLastIndex = attachment.fileName.lastIndexOf('.');
|
||||
if (dotLastIndex !== -1) {
|
||||
return attachment.fileName.substring(dotLastIndex + 1);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
switch (attachment.contentType) {
|
||||
|
|
|
@ -9,7 +9,6 @@ import {
|
|||
IMAGE_WEBP,
|
||||
MIMEType,
|
||||
} from '../types/MIME';
|
||||
import { PromiseUtils } from '../session/utils';
|
||||
|
||||
const MAX_REQUEST_COUNT_WITH_REDIRECTS = 20;
|
||||
// tslint:disable: prefer-for-of
|
||||
|
|
|
@ -4239,14 +4239,6 @@
|
|||
"reasonCategory": "falseMatch",
|
||||
"updated": "2018-09-19T18:13:29.628Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-append(",
|
||||
"path": "node_modules/node-fetch/lib/headers.js",
|
||||
"line": "\t\t\t\tself.append(prop, item.toString());",
|
||||
"lineNumber": 40,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2018-09-19T18:13:29.628Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "node_modules/node-forge/dist/forge.all.min.js",
|
||||
|
|
Loading…
Reference in New Issue