Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
784ef18836 | ||
|
|
5086745d8b | ||
|
|
ff673b0a9f | ||
|
|
5dc6c76750 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
build/
|
||||||
15
.idea/chat-example.iml
generated
Normal file
15
.idea/chat-example.iml
generated
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="TemplatesService">
|
||||||
|
<option name="TEMPLATE_FOLDERS">
|
||||||
|
<list>
|
||||||
|
<option value="$MODULE_DIR$/node_modules/istanbul-reports/lib/html/templates" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
4
.idea/encodings.xml
generated
Normal file
4
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
|
||||||
|
</project>
|
||||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptSettings">
|
||||||
|
<option name="languageLevel" value="JSX" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.5" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/chat-example.iml" filepath="$PROJECT_DIR$/.idea/chat-example.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
85
.idea/workspace.xml
generated
Normal file
85
.idea/workspace.xml
generated
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="9cc67e50-00db-4a45-8df7-04c3fb0d4132" name="Default Changelist" comment="">
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/Chat.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/Chat.js" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/MessageList.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/MessageList.js" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectFrameBounds">
|
||||||
|
<option name="x" value="260" />
|
||||||
|
<option name="y" value="20" />
|
||||||
|
<option name="width" value="1400" />
|
||||||
|
<option name="height" value="1000" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectView">
|
||||||
|
<navigator proportions="" version="1">
|
||||||
|
<foldersAlwaysOnTop value="true" />
|
||||||
|
</navigator>
|
||||||
|
<panes>
|
||||||
|
<pane id="Scope" />
|
||||||
|
<pane id="ProjectPane" />
|
||||||
|
</panes>
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">
|
||||||
|
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||||
|
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
|
||||||
|
<property name="nodejs_npm_path_reset_for_default_project" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="RunDashboard">
|
||||||
|
<option name="ruleStates">
|
||||||
|
<list>
|
||||||
|
<RuleState>
|
||||||
|
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
|
||||||
|
</RuleState>
|
||||||
|
<RuleState>
|
||||||
|
<option name="name" value="StatusDashboardGroupingRule" />
|
||||||
|
</RuleState>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="9cc67e50-00db-4a45-8df7-04c3fb0d4132" name="Default Changelist" comment="" />
|
||||||
|
<created>1545684502781</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1545684502781</updated>
|
||||||
|
<workItem from="1545684514629" duration="4000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TimeTrackingManager">
|
||||||
|
<option name="totallyTimeSpent" value="4000" />
|
||||||
|
</component>
|
||||||
|
<component name="ToolWindowManager">
|
||||||
|
<frame x="260" y="20" width="1400" height="1000" extended-state="0" />
|
||||||
|
<layout>
|
||||||
|
<window_info id="Favorites" side_tool="true" />
|
||||||
|
<window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.25" />
|
||||||
|
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
|
||||||
|
<window_info anchor="bottom" id="Database Changes" />
|
||||||
|
<window_info anchor="bottom" id="Version Control" />
|
||||||
|
<window_info anchor="bottom" id="Python Console" />
|
||||||
|
<window_info anchor="bottom" id="Terminal" />
|
||||||
|
<window_info anchor="bottom" id="Event Log" side_tool="true" />
|
||||||
|
<window_info anchor="bottom" id="Message" order="0" />
|
||||||
|
<window_info anchor="bottom" id="Find" order="1" />
|
||||||
|
<window_info anchor="bottom" id="Run" order="2" />
|
||||||
|
<window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
|
||||||
|
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
|
||||||
|
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
|
||||||
|
<window_info anchor="bottom" id="TODO" order="6" />
|
||||||
|
<window_info anchor="right" id="SciView" />
|
||||||
|
<window_info anchor="right" id="Database" />
|
||||||
|
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
|
||||||
|
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
|
||||||
|
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
|
||||||
|
</layout>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
13168
package-lock.json
generated
Normal file
13168
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
package.json
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"main": "src/electron-starter.js",
|
||||||
|
"devDependencies": {
|
||||||
|
"electron": "^2.0.1",
|
||||||
|
"foreman": "^2.0.0",
|
||||||
|
"react-scripts": "^1.0.14"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@pusher/chatkit": "^0.7.18",
|
||||||
|
"add": "^2.0.6",
|
||||||
|
"axios": "^0.18.0",
|
||||||
|
"body-parser": "^1.18.3",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"express": "^4.16.4",
|
||||||
|
"libsignal": "^2.0.0",
|
||||||
|
"pusher-chatkit-server": "^0.12.0",
|
||||||
|
"react": "^16.0.0",
|
||||||
|
"react-desktop": "^0.3.7",
|
||||||
|
"react-dom": "^16.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test --env=jsdom",
|
||||||
|
"electron": "electron .",
|
||||||
|
"dev": "nf start -p 3002"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
public/index.html
Normal file
12
public/index.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||||
|
<title>App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
329
server.js
Normal file
329
server.js
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
// ./server.js
|
||||||
|
|
||||||
|
const express = require('express')
|
||||||
|
const bodyParser = require('body-parser')
|
||||||
|
const cors = require('cors')
|
||||||
|
const Chatkit = require('pusher-chatkit-server')
|
||||||
|
|
||||||
|
var libsignal = require('libsignal');
|
||||||
|
|
||||||
|
const chatkit = new Chatkit.default({
|
||||||
|
instanceLocator: 'v1:us1:366480cc-91f1-4cbd-8c04-68dc96e8ccdf',
|
||||||
|
key: '982eaea8-66f5-4bdb-9a2d-14de0fa8b92b:WSgfMJzA8cvZ/fwEqjupJhOpnwQ7qbAEOVze44y+XK0='
|
||||||
|
})
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
app.use(bodyParser.urlencoded({
|
||||||
|
extended: false
|
||||||
|
}))
|
||||||
|
app.use(bodyParser.json())
|
||||||
|
app.use(cors())
|
||||||
|
//--------
|
||||||
|
|
||||||
|
var KeyHelper = libsignal.keyhelper;
|
||||||
|
var sessionPromise = null;
|
||||||
|
|
||||||
|
function SignalProtocolStore() {
|
||||||
|
this.store = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalProtocolStore.prototype = {
|
||||||
|
Direction: {
|
||||||
|
SENDING: 1,
|
||||||
|
RECEIVING: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
getIdentityKeyPair: function () {
|
||||||
|
return Promise.resolve(this.get('identityKey'));
|
||||||
|
},
|
||||||
|
//umetnuta nova fun
|
||||||
|
getOurIdentity: function () {
|
||||||
|
return Promise.resolve(this.get('identityKey'));
|
||||||
|
},
|
||||||
|
getLocalRegistrationId: function () {
|
||||||
|
return Promise.resolve(this.get('registrationId'));
|
||||||
|
},
|
||||||
|
getOurRegistrationId: function () {
|
||||||
|
return Promise.resolve(this.get('identityKey'));
|
||||||
|
},
|
||||||
|
put: function (key, value) {
|
||||||
|
if (key === undefined || value === undefined || key === null || value === null)
|
||||||
|
throw new Error("Tried to store undefined/null");
|
||||||
|
this.store[key] = value;
|
||||||
|
},
|
||||||
|
get: function (key, defaultValue) {
|
||||||
|
if (key === null || key === undefined)
|
||||||
|
throw new Error("Tried to get value for undefined/null key");
|
||||||
|
if (key in this.store) {
|
||||||
|
return this.store[key];
|
||||||
|
} else {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remove: function (key) {
|
||||||
|
if (key === null || key === undefined)
|
||||||
|
throw new Error("Tried to remove value for undefined/null key");
|
||||||
|
delete this.store[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
isTrustedIdentity: function (identifier, identityKey, direction) {
|
||||||
|
if (identifier === null || identifier === undefined) {
|
||||||
|
throw new Error("tried to check identity key for undefined/null key");
|
||||||
|
}
|
||||||
|
if (!(identityKey instanceof ArrayBuffer)) {
|
||||||
|
identityKey = identityKey.pubKey;
|
||||||
|
//throw new Error("Expected identityKey to be an ArrayBuffer");
|
||||||
|
}
|
||||||
|
var trusted = this.get('identityKey' + identifier);
|
||||||
|
if (trusted === undefined) {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
return Promise.resolve(util.toString(identityKey) === util.toString(trusted));
|
||||||
|
},
|
||||||
|
loadIdentityKey: function (identifier) {
|
||||||
|
if (identifier === null || identifier === undefined)
|
||||||
|
throw new Error("Tried to get identity key for undefined/null key");
|
||||||
|
return Promise.resolve(this.get('identityKey' + identifier));
|
||||||
|
},
|
||||||
|
saveIdentity: function (identifier, identityKey) {
|
||||||
|
if (identifier === null || identifier === undefined)
|
||||||
|
throw new Error("Tried to put identity key for undefined/null key");
|
||||||
|
|
||||||
|
var address = new libsignal.SignalProtocolAddress.fromString(identifier);
|
||||||
|
|
||||||
|
var existing = this.get('identityKey' + address.getName());
|
||||||
|
this.put('identityKey' + address.getName(), identityKey)
|
||||||
|
|
||||||
|
if (existing && util.toString(identityKey) !== util.toString(existing)) {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Returns a prekeypair object or undefined */
|
||||||
|
loadPreKey: function (keyId) {
|
||||||
|
var res = this.get('25519KeypreKey' + keyId);
|
||||||
|
if (res !== undefined) {
|
||||||
|
res = {
|
||||||
|
pubKey: res.pubKey,
|
||||||
|
privKey: res.privKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return Promise.resolve(res);
|
||||||
|
},
|
||||||
|
storePreKey: function (keyId, keyPair) {
|
||||||
|
return Promise.resolve(this.put('25519KeypreKey' + keyId, keyPair));
|
||||||
|
},
|
||||||
|
removePreKey: function (keyId) {
|
||||||
|
return Promise.resolve(this.remove('25519KeypreKey' + keyId));
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Returns aa signed keypair object or undefined */
|
||||||
|
loadSignedPreKey: function (keyId) {
|
||||||
|
var res = this.get('25519KeysignedKey' + keyId);
|
||||||
|
if (res !== undefined) {
|
||||||
|
res = {
|
||||||
|
pubKey: res.pubKey,
|
||||||
|
privKey: res.privKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return Promise.resolve(res);
|
||||||
|
},
|
||||||
|
storeSignedPreKey: function (keyId, keyPair) {
|
||||||
|
return Promise.resolve(this.put('25519KeysignedKey' + keyId, keyPair));
|
||||||
|
},
|
||||||
|
removeSignedPreKey: function (keyId) {
|
||||||
|
return Promise.resolve(this.remove('25519KeysignedKey' + keyId));
|
||||||
|
},
|
||||||
|
|
||||||
|
loadSession: function (identifier) {
|
||||||
|
return Promise.resolve(this.get('session' + identifier));
|
||||||
|
},
|
||||||
|
storeSession: function (identifier, record) {
|
||||||
|
return Promise.resolve(this.put('session' + identifier, record));
|
||||||
|
},
|
||||||
|
removeSession: function (identifier) {
|
||||||
|
return Promise.resolve(this.remove('session' + identifier));
|
||||||
|
},
|
||||||
|
removeAllSessions: function (identifier) {
|
||||||
|
for (var id in this.store) {
|
||||||
|
if (id.startsWith('session' + identifier)) {
|
||||||
|
delete this.store[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var senderStore = new SignalProtocolStore();
|
||||||
|
|
||||||
|
var receiverStore = new SignalProtocolStore();
|
||||||
|
//---------------
|
||||||
|
function generateIdentity(store) {
|
||||||
|
return Promise.all([
|
||||||
|
KeyHelper.generateIdentityKeyPair(),
|
||||||
|
KeyHelper.generateRegistrationId(),
|
||||||
|
]).then(function (result) {
|
||||||
|
store.put('identityKey', result[0]);
|
||||||
|
store.put('registrationId', result[1]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePreKeyBundle(store, preKeyId, signedPreKeyId) {
|
||||||
|
return Promise.all([
|
||||||
|
store.getIdentityKeyPair(),
|
||||||
|
store.getLocalRegistrationId()
|
||||||
|
]).then(function (result) {
|
||||||
|
var identity = result[0];
|
||||||
|
var registrationId = result[1];
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
KeyHelper.generatePreKey(preKeyId),
|
||||||
|
KeyHelper.generateSignedPreKey(identity, signedPreKeyId),
|
||||||
|
]).then(function (keys) {
|
||||||
|
var preKey = keys[0]
|
||||||
|
var signedPreKey = keys[1];
|
||||||
|
|
||||||
|
store.storePreKey(preKeyId, preKey.keyPair);
|
||||||
|
store.storeSignedPreKey(signedPreKeyId, signedPreKey.keyPair);
|
||||||
|
|
||||||
|
return {
|
||||||
|
identityKey: identity.pubKey,
|
||||||
|
registrationId: registrationId,
|
||||||
|
preKey: {
|
||||||
|
keyId: preKeyId,
|
||||||
|
publicKey: preKey.keyPair.pubKey
|
||||||
|
},
|
||||||
|
signedPreKey: {
|
||||||
|
keyId: signedPreKeyId,
|
||||||
|
publicKey: signedPreKey.keyPair.pubKey,
|
||||||
|
signature: signedPreKey.signature
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//---------
|
||||||
|
|
||||||
|
app.post("/cryptMessage", async (req, res) => {
|
||||||
|
|
||||||
|
//sessionPromise=req.body.data.sessionPromise;
|
||||||
|
message = req.body.data.message;
|
||||||
|
//senderStore = req.body.data.senderStore;
|
||||||
|
//RECEIVER_ADDRESS = new libsignal.ProtocolAddress(req.body.data.receiverAdress.id, req.body.data.receiverAdress.deviceId);
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
|
||||||
|
var senderAdress = req.body.data.senderAdress; //"xxxxxxxxx"
|
||||||
|
var receiverAdress = req.body.data.receiverAdress; //""yyyyyyyyyyyyy""
|
||||||
|
var receiverPreKeyId = req.body.data.receiverPreKeyId; // 1337;
|
||||||
|
var receiverSignedKeyId = req.body.data.receiverSignedKeyId; // 1;
|
||||||
|
|
||||||
|
var SENDER_ADDRESS = new libsignal.ProtocolAddress(senderAdress, 1);
|
||||||
|
var RECEIVER_ADDRESS = new libsignal.ProtocolAddress(receiverAdress, 1);
|
||||||
|
|
||||||
|
var receiverSignedKeyId = 1;
|
||||||
|
|
||||||
|
var Curve = libsignal.Curve;
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
generateIdentity(senderStore),
|
||||||
|
generateIdentity(receiverStore),
|
||||||
|
]).then(function () {
|
||||||
|
return generatePreKeyBundle(receiverStore, receiverPreKeyId, receiverSignedKeyId);
|
||||||
|
}).then(function (preKeyBundle) {
|
||||||
|
var builder = new libsignal.SessionBuilder(senderStore, RECEIVER_ADDRESS);
|
||||||
|
sessionPromise = builder.initOutgoing(preKeyBundle);
|
||||||
|
sessionPromise.then(function () {
|
||||||
|
|
||||||
|
var originalMessage = Buffer.from(message, 'utf8');
|
||||||
|
var senderSessionCipher = new libsignal.SessionCipher(senderStore, RECEIVER_ADDRESS);
|
||||||
|
|
||||||
|
//var receiverSessionCipher = new libsignal.SessionCipher(receiverStore, SENDER_ADDRESS);
|
||||||
|
|
||||||
|
senderSessionCipher.encrypt(originalMessage).then(function (ciphertext) {
|
||||||
|
console.log("KRI, RECSTORE:", receiverStore);
|
||||||
|
console.log("KRI, RECadress:", SENDER_ADDRESS);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
data: {
|
||||||
|
receiverStore: receiverStore,
|
||||||
|
SENDER_ADDRESS: SENDER_ADDRESS,
|
||||||
|
message: ciphertext.body
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
app.post('/deCryptMessage', (req, res) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message = req.body.data.message;
|
||||||
|
const buf5 = Buffer.from(message.split(','));
|
||||||
|
//receiverStore = req.body.data.receiverStore;
|
||||||
|
//SENDER_ADDRESS = req.body.data.SENDER_ADDRESS;
|
||||||
|
senderAdress = req.body.data.senderAdress;
|
||||||
|
var SENDER_ADDRESS = new libsignal.ProtocolAddress(senderAdress, 1);
|
||||||
|
console.log("DEKRI, RECSTORE:", receiverStore.store.identityKey.pubKey);
|
||||||
|
console.log("DEKRI, RECSTORE:", SENDER_ADDRESS);
|
||||||
|
//var receiverSessionCipher = new libsignal.SessionCipher(receiverStore, SENDER_ADDRESS);
|
||||||
|
|
||||||
|
//odavde
|
||||||
|
|
||||||
|
var receiverSessionCipher = new libsignal.SessionCipher(receiverStore, SENDER_ADDRESS); //ubaceno
|
||||||
|
receiverSessionCipher.decryptPreKeyWhisperMessage(buf5, 'binary').then(function (ciphertext) {
|
||||||
|
poruka = ciphertext.toString();
|
||||||
|
console.log(poruka)
|
||||||
|
res.json({
|
||||||
|
data: {
|
||||||
|
message: poruka
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.post('/users', (req, res) => {
|
||||||
|
const {
|
||||||
|
username
|
||||||
|
} = req.body
|
||||||
|
const user = {
|
||||||
|
name: username,
|
||||||
|
id: username
|
||||||
|
}
|
||||||
|
chatkit
|
||||||
|
.createUser(user)
|
||||||
|
.then(() => {
|
||||||
|
console.log('Created user ', user.name)
|
||||||
|
res.status(201).json(user)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (error.error === 'services/chatkit/user_already_exists') {
|
||||||
|
console.log('User already exists ', user.name)
|
||||||
|
res.status(201).json(user)
|
||||||
|
} else {
|
||||||
|
console.error(error)
|
||||||
|
res.status(error.status).json(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app.listen(3001)
|
||||||
|
console.log('Running on port 3001')
|
||||||
45
src/App.js
Normal file
45
src/App.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// ./src/App.js
|
||||||
|
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import UsernameForm from './UsernameForm'
|
||||||
|
import Chat from './Chat'
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
state = {
|
||||||
|
currentUsername: null,
|
||||||
|
currentId: null,
|
||||||
|
currentScreen: 'usernameForm'
|
||||||
|
}
|
||||||
|
|
||||||
|
onUsernameSubmitted = username => {
|
||||||
|
fetch('http://localhost:3001/users', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
this.setState({
|
||||||
|
currentId: data.id,
|
||||||
|
currentUsername: data.name,
|
||||||
|
currentScreen: 'chat'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('error', error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.currentScreen === 'usernameForm') {
|
||||||
|
return <UsernameForm handleSubmit={this.onUsernameSubmitted} />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.currentScreen === 'chat') {
|
||||||
|
return <Chat currentId={this.state.currentId} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default App;
|
||||||
240
src/Chat.js
Normal file
240
src/Chat.js
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
// ./src/Chat.js
|
||||||
|
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import { ChatManager, TokenProvider } from '@pusher/chatkit'
|
||||||
|
import MessageList from './MessageList'
|
||||||
|
import SendMessageForm from './SendMessageForm'
|
||||||
|
import OnlineList from './OnlineList'
|
||||||
|
//import libsignal from 'libsignal'
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
|
||||||
|
class Chat extends Component {
|
||||||
|
state = {
|
||||||
|
currentUser: null,
|
||||||
|
currentRoom: {},
|
||||||
|
poruka: "",
|
||||||
|
messages: [],
|
||||||
|
receiverStore: null,
|
||||||
|
SENDER_ADDRESS: null,
|
||||||
|
message: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const chatkit = new ChatManager({
|
||||||
|
instanceLocator: 'v1:us1:366480cc-91f1-4cbd-8c04-68dc96e8ccdf',
|
||||||
|
userId: this.props.currentId,
|
||||||
|
tokenProvider: new TokenProvider({
|
||||||
|
url:
|
||||||
|
'https://us1.pusherplatform.io/services/chatkit_token_provider/v1/366480cc-91f1-4cbd-8c04-68dc96e8ccdf/token'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
chatkit
|
||||||
|
.connect()
|
||||||
|
.then(currentUser => {
|
||||||
|
this.setState({ currentUser })
|
||||||
|
console.log('Bleep bloop 🤖 You are connected to Chatkit')
|
||||||
|
return currentUser.subscribeToRoom({
|
||||||
|
roomId: 19378901, // Replace with YOUR ROOM ID
|
||||||
|
messageLimit: 100,
|
||||||
|
hooks: {
|
||||||
|
onNewMessage: message => {
|
||||||
|
|
||||||
|
if (message != "") {
|
||||||
|
console.log("ČITAVA PORUKA:", message)
|
||||||
|
this.onDeCryptingMessage2(message);
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OnUserCameOnline: () => this.forceUpdate(),
|
||||||
|
onUserWentOffline: () => this.forceUpdate(),
|
||||||
|
onUserJoined: () => this.forceUpdate()
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(currentRoom => {
|
||||||
|
this.setState({ currentRoom })
|
||||||
|
})
|
||||||
|
.catch(error => console.error('error', error))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onSend = text => {
|
||||||
|
//ubaceno
|
||||||
|
console.log("Primljena poruka:", text);
|
||||||
|
fetch('http://localhost:3001/cryptMessage', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
data: {
|
||||||
|
senderAdress: "xxxxxxxxx",
|
||||||
|
receiverAdress: "yyyyyyyyyyyyy",
|
||||||
|
receiverPreKeyId: 1337,
|
||||||
|
receiverSignedKeyId: 1,
|
||||||
|
message: text
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => { console.log("REPSONSE:", response); return response.json(); })
|
||||||
|
.then(data => {
|
||||||
|
this.state.poruka = data.data.message.data.toString();
|
||||||
|
this.setState({
|
||||||
|
receiverStore: data.data.receiverStore,
|
||||||
|
SENDER_ADDRESS: data.data.SENDER_ADDRESS
|
||||||
|
})
|
||||||
|
this.state.currentUser.sendMessage({
|
||||||
|
text:this.state.poruka,
|
||||||
|
roomId: this.state.currentRoom.id
|
||||||
|
})
|
||||||
|
console.log("PORUKA:",this.state.poruka);
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('error', error)
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('error', error)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
//kraj
|
||||||
|
/*
|
||||||
|
console.log("TEKST:", text);
|
||||||
|
this.state.currentUser.sendMessage({
|
||||||
|
text,
|
||||||
|
roomId: this.state.currentRoom.id
|
||||||
|
})*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onDeCryptingMessage2 = message => {
|
||||||
|
|
||||||
|
console.log("DEKRIPCIJA, poruka primljena:", message);
|
||||||
|
fetch('http://localhost:3001/decryptMessage', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
data: {
|
||||||
|
receiverStore: this.state.receiverStore,
|
||||||
|
SENDER_ADDRESS: this.state.SENDER_ADDRESS,
|
||||||
|
message: message.text,
|
||||||
|
senderAdress: "xxxxxxxxx"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => { console.log("REPSONSE:", response); return response.json(); })
|
||||||
|
.then(data => {
|
||||||
|
message.text = data.data.message
|
||||||
|
this.state.message = message;
|
||||||
|
console.log("PORUKA 3:",this.state.message)
|
||||||
|
this.setState({
|
||||||
|
messages: [...this.state.messages, this.state.message]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('error', error)
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('error', error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="wrapper">
|
||||||
|
<div>
|
||||||
|
<OnlineList
|
||||||
|
currentUser={this.state.currentUser}
|
||||||
|
users={this.state.currentRoom.users}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="chat">
|
||||||
|
<MessageList messages={this.state.messages} />
|
||||||
|
<SendMessageForm onSend={this.onSend} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Chat
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
onCryptingMessage = message => {
|
||||||
|
fetch('http://localhost:3001/cryptMessage', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
data: {
|
||||||
|
senderAdress: "xxxxxxxxx",
|
||||||
|
receiverAdress: "yyyyyyyyyyyyy",
|
||||||
|
receiverPreKeyId: 1337,
|
||||||
|
receiverSignedKeyId: 1,
|
||||||
|
message: message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => { console.log("REPSONSE:", response); return response.json(); })
|
||||||
|
.then(data => {
|
||||||
|
console.log("DATA:", data.data)
|
||||||
|
this.setState({
|
||||||
|
receiverStore: data.data.receiverStore,
|
||||||
|
senderAdress: data.data.senderAdress,
|
||||||
|
poruka: data.data.message
|
||||||
|
})
|
||||||
|
|
||||||
|
this.onSend(data.data.message.data.toString())
|
||||||
|
return data.data.message
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('error', error)
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('error', error)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
/*onDeCryptingMessage = message => {
|
||||||
|
|
||||||
|
console.log("DEKRIPCIJA, poruka primljena:", message);
|
||||||
|
fetch('http://localhost:3001/decryptMessage2', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
data: {
|
||||||
|
senderAdress: "xxxxxxxxx",
|
||||||
|
receiverAdress: "yyyyyyyyyyyyy",
|
||||||
|
receiverPreKeyId: 1337,
|
||||||
|
receiverSignedKeyId: 1,
|
||||||
|
message: message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => { console.log("REPSONSE:", response); return response.json(); })
|
||||||
|
.then(data => {
|
||||||
|
console.log("DATA:", data.data)
|
||||||
|
this.setState({
|
||||||
|
message: data.data.message
|
||||||
|
})
|
||||||
|
this.setState({
|
||||||
|
messages: [...this.state.messages, message]
|
||||||
|
})
|
||||||
|
return data.data.message
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('error', error)
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('error', error)
|
||||||
|
})
|
||||||
|
} */
|
||||||
55
src/MessageList.js
Normal file
55
src/MessageList.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// ./src/MessageList.js
|
||||||
|
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import {
|
||||||
|
ListView,
|
||||||
|
ListViewSection,
|
||||||
|
ListViewSectionHeader,
|
||||||
|
ListViewRow,
|
||||||
|
Text
|
||||||
|
} from 'react-desktop/macOs'
|
||||||
|
|
||||||
|
class MessageList extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<ListView>
|
||||||
|
<ListViewSection>
|
||||||
|
{this.props.messages.map((message, index) =>
|
||||||
|
this.renderItem(message)
|
||||||
|
)}
|
||||||
|
</ListViewSection>
|
||||||
|
</ListView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderItem(message) {
|
||||||
|
return (
|
||||||
|
<ListViewRow key={message.id}>
|
||||||
|
<Text color="#414141" size="13" bold>
|
||||||
|
{message.sender.name}:
|
||||||
|
</Text>
|
||||||
|
<Text color="#414141" size="13">
|
||||||
|
{message.text}
|
||||||
|
</Text>
|
||||||
|
</ListViewRow>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MessageList
|
||||||
|
|
||||||
|
/*
|
||||||
|
renderItem(message) {
|
||||||
|
return (
|
||||||
|
<ListViewRow key={message.id}>
|
||||||
|
<Text color="#414141" size="13" bold>
|
||||||
|
{message.sender.name}:
|
||||||
|
</Text>
|
||||||
|
<Text color="#414141" size="13">
|
||||||
|
{message.text}
|
||||||
|
</Text>
|
||||||
|
</ListViewRow>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
51
src/OnlineList.js
Normal file
51
src/OnlineList.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// ./src/OnlineList.js
|
||||||
|
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import {
|
||||||
|
ListView,
|
||||||
|
ListViewSection,
|
||||||
|
ListViewSectionHeader,
|
||||||
|
ListViewRow,
|
||||||
|
Text
|
||||||
|
} from 'react-desktop/macOs'
|
||||||
|
|
||||||
|
class OnlineList extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<ListView className="online-list">
|
||||||
|
<ListViewSection>
|
||||||
|
{this.props.users &&
|
||||||
|
this.props.users.map((user, index) => {
|
||||||
|
if (user.id === this.props.currentUser.id) {
|
||||||
|
return this.renderItem(
|
||||||
|
`${user.name} (You)`,
|
||||||
|
user.id,
|
||||||
|
user.presence.state
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return this.renderItem(user.name, user.id, user.presence.state)
|
||||||
|
})}
|
||||||
|
</ListViewSection>
|
||||||
|
</ListView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderItem(name, id, status) {
|
||||||
|
const itemStyle = {}
|
||||||
|
return (
|
||||||
|
<ListViewRow key={id}>
|
||||||
|
<div
|
||||||
|
className="online-list-item"
|
||||||
|
style={{
|
||||||
|
background: status === 'online' ? '#6BD761' : 'gray'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text color="#414141" size="13">
|
||||||
|
{name}{' '}
|
||||||
|
</Text>{' '}
|
||||||
|
</ListViewRow>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OnlineList
|
||||||
43
src/SendMessageForm.js
Normal file
43
src/SendMessageForm.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// ./src/SendMessageForm.js
|
||||||
|
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import { Button, TextInput } from 'react-desktop/macOs'
|
||||||
|
|
||||||
|
class SendMessageForm extends Component {
|
||||||
|
state = {
|
||||||
|
text: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit = e => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.props.onSend(this.state.text)
|
||||||
|
this.setState({ text: '' })
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange = e => {
|
||||||
|
this.setState({ text: e.target.value })
|
||||||
|
if (this.props.onChange) {
|
||||||
|
this.props.onChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="send-message-form-container">
|
||||||
|
<form onSubmit={this.onSubmit} className="send-message-form">
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
onChange={this.onChange}
|
||||||
|
value={this.state.text}
|
||||||
|
className="message-input"
|
||||||
|
/>
|
||||||
|
<Button color="blue" type="submit">
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SendMessageForm
|
||||||
47
src/UsernameForm.js
Normal file
47
src/UsernameForm.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// ./src/UsernameForm.js
|
||||||
|
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import { TextInput } from 'react-desktop/macOs'
|
||||||
|
import { Button } from 'react-desktop/macOs'
|
||||||
|
|
||||||
|
class UsernameForm extends Component {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.state = {
|
||||||
|
username: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//19374147
|
||||||
|
handleSubmit = e => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.props.handleSubmit(this.state.username)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = e => {
|
||||||
|
this.setState({ username: e.target.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="username-form">
|
||||||
|
<form onSubmit={this.handleSubmit}>
|
||||||
|
<div>
|
||||||
|
<TextInput
|
||||||
|
label="Username:"
|
||||||
|
placeholder="For example, @bookercodes"
|
||||||
|
value={this.state.username}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button color="blue" type="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UsernameForm
|
||||||
24
src/electron-react.js
Normal file
24
src/electron-react.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const net = require('net')
|
||||||
|
const port = process.env.PORT ? process.env.PORT - 100 : 3002
|
||||||
|
|
||||||
|
process.env.ELECTRON_START_URL = `http://localhost:${port}`
|
||||||
|
|
||||||
|
const client = new net.Socket()
|
||||||
|
|
||||||
|
let startedElectron = false
|
||||||
|
const tryConnection = () =>
|
||||||
|
client.connect({ port: port }, () => {
|
||||||
|
client.end()
|
||||||
|
if (!startedElectron) {
|
||||||
|
console.log('starting electron')
|
||||||
|
startedElectron = true
|
||||||
|
const exec = require('child_process').exec
|
||||||
|
exec('npm run electron')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
tryConnection()
|
||||||
|
|
||||||
|
client.on('error', error => {
|
||||||
|
setTimeout(tryConnection, 1000)
|
||||||
|
})
|
||||||
39
src/electron-starter.js
Normal file
39
src/electron-starter.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
const electron = require('electron')
|
||||||
|
const app = electron.app
|
||||||
|
const BrowserWindow = electron.BrowserWindow
|
||||||
|
|
||||||
|
const path = require('path')
|
||||||
|
const url = require('url')
|
||||||
|
|
||||||
|
let mainWindow
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
mainWindow = new BrowserWindow({ width: 800, height: 600 })
|
||||||
|
|
||||||
|
const startUrl =
|
||||||
|
process.env.ELECTRON_START_URL ||
|
||||||
|
url.format({
|
||||||
|
pathname: path.join(__dirname, '/../build/index.html'),
|
||||||
|
protocol: 'file:',
|
||||||
|
slashes: true
|
||||||
|
})
|
||||||
|
mainWindow.loadURL(startUrl)
|
||||||
|
|
||||||
|
mainWindow.on('closed', function() {
|
||||||
|
mainWindow = null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
app.on('ready', createWindow)
|
||||||
|
|
||||||
|
app.on('window-all-closed', function() {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('activate', function() {
|
||||||
|
if (mainWindow === null) {
|
||||||
|
createWindow()
|
||||||
|
}
|
||||||
|
})
|
||||||
56
src/index.css
Normal file
56
src/index.css
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/* ./src/index.css */
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: BlinkMacSystemFont, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.username-form {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.username-form button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.online-list {
|
||||||
|
/* box-shadow: 0 -8px 20px 2px #DEDEE3; */
|
||||||
|
background: #F9F9FB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat {
|
||||||
|
/* background: #F4FAFE; */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.send-message-form{
|
||||||
|
padding: 10px 15px 10px 15px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-message-form div {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.online-list-item {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li span {
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
6
src/index.js
Normal file
6
src/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import App from './App'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, document.getElementById('app'))
|
||||||
Reference in New Issue
Block a user