Compare commits

...

2 Commits

@ -0,0 +1,28 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch debugging in Firefox",
"type": "firefox",
"request": "launch",
"reAttach": true,
"url": "http://localhost:3000/",
"webRoot": "${workspaceFolder}"
},
{
"name": "Attach",
"type": "firefox",
"request": "attach"
},
{
"name": "Launch WebExtension",
"type": "firefox",
"request": "launch",
"reAttach": true,
"addonPath": "${workspaceFolder}"
}
]
}

@ -53,31 +53,35 @@ let App = create({
this.setState({ this.setState({
client: client client: client
}); });
this.startClient(client); this.startClient(client);
}, },
updateRooms: function (client) {
let rooms = {};
client.getRooms().forEach((room) => {
rooms[room.roomId] = room;
});
this.setState({rooms: rooms});
},
startClient: function(client) { startClient: function(client) {
console.log(client); console.log(client);
client.on("sync", (state, _prevState, _data) => { client.on("sync", (state, _prevState, _data) => {
if (state == "ERROR") { if (state == "ERROR") {
/* FIXME: Implement? */ /* FIXME: Implement? */
} else if (state == "SYNCING") { } else if (state == "SYNCING") {
let rooms = {}; this.updateRooms(client);
client.getRooms().forEach((room) => {
rooms[room.roomId] = room;
});
this.setState({rooms: rooms});
} else if (state == "PREPARED") { } else if (state == "PREPARED") {
/* FIXME: Implement? */ /* FIXME: Implement? */
} }
}); });
client.on("Room.localEchoUpdated", (_event) => { client.on("Room.localEchoUpdated", (_event) => {
let rooms = {}; this.updateRooms(client);
client.getRooms().forEach((room) => {
rooms[room.roomId] = room;
});
this.setState({rooms: rooms});
}); });
client.startClient(); client.startClient();
@ -87,13 +91,14 @@ let App = create({
if (this.state.client == undefined) { if (this.state.client == undefined) {
//Login screen //Login screen
return <Login callback={this.loginCallback}/>; return <Login callback={this.loginCallback}/>;
} else {
return (
<>
<Sidebar options={this.state.options} client={this.state.client} rooms={this.state.rooms} selectRoom={(roomId) => {this.setState({roomId: roomId});}}/>
<Chat client={this.state.client} roomId={this.state.roomId}/>
</>
);
} }
return (
<>
<Sidebar options={this.state.options} client={this.state.client} rooms={this.state.rooms} selectRoom={(roomId) => {this.setState({roomId: roomId});}}/>
<Chat client={this.state.client} roomId={this.state.roomId}/>
</>
);
} }
}); });

@ -4,6 +4,8 @@ const create = require('create-react-class');
const Promise = require('bluebird'); const Promise = require('bluebird');
const urllib = require('url'); const urllib = require('url');
const createApiRequester = require("../lib/api-request");
let login = create({ let login = create({
displayName: "Login", displayName: "Login",
@ -24,67 +26,82 @@ let login = create({
}, },
login: function() { login: function() {
this.setState({error: ""}); return Promise.try(() => {
this.setState({error: ""});
if (this.state.hs.valid) {
return this.doLogin();
}
let parts = this.state.formState.user.split(':');
if (parts.length != 2) {
return this.setState({error: "Please enter a full mxid, like username:homeserver.tld"});
}
let hostname = urllib.parse("https://" + parts[1]);
return Promise.try(() => {
return getApiServer(hostname);
}).then((homeserverUrl) => {
console.log("Using API server", homeserverUrl);
if (this.state.hs.valid) { this.setState({
return this.doLogin(); apiUrl: homeserverUrl,
} apiRequest: createApiRequester(homeserverUrl),
formState: Object.assign(this.state.formState, {
user: parts[0],
hs: homeserverUrl
}),
hs: Object.assign(this.state.hs, {
valid: true
})
});
let parts = this.state.formState.user.split(':'); return this.doLogin();
if (parts.length != 2) { }).catch((error) => {
return this.setState({error: "Please enter a full mxid, like username:homeserver.tld"}); /* FIXME: Error filtering */
} console.log("ERROR fetching homeserver url", error);
let hostname = urllib.parse("https://" + parts[1]); this.setState({
getApiServer(hostname).then((hs) => { hs: Object.assign(this.state.hs, {
console.log("Using API server", hs); error: error,
let formState = this.state.formState; valid: false,
formState.user = parts[0]; prompt: true
formState.hs = hs; })
let hsState = Object.assign(this.state.hs, {valid: true}); });
this.setState({apiUrl: hs, formState: formState, hs: hsState}); });
this.doLogin();
}).catch((error) => {
console.log("ERROR fetching homeserver url", error);
let hsState = Object.assign(this.state.hs, {error: error, valid: false, prompt: true});
this.setState({hs: hsState});
}); });
}, },
doLogin: function() { doLogin: function() {
console.log("Logging in"); return Promise.try(() => {
let user = this.state.formState.user.replace('@', ''); console.log("Logging in");
let password = this.state.formState.pass; let user = this.state.formState.user.replace('@', '');
let hs = this.state.apiUrl; let password = this.state.formState.pass;
let homeserverUrl = this.state.apiUrl;
let data = {
user: user, return Promise.try(() => {
password: password, return this.state.apiRequest("/_matrix/client/r0/login", {
type: "m.login.password", user: user,
initial_device_display_name: "Neo v4", password: password,
}; type: "m.login.password",
initial_device_display_name: "Neo v4",
});
}).then((responseJson) => {
console.log("got access token", responseJson);
let url = hs + "/_matrix/client/r0/login"; this.setState({ json: responseJson });
fetch(url, {
body: JSON.stringify(data),
headers: {
'content-type': 'application/json'
},
method: 'POST',
}).then((response) => response.json())
.then((responseJson) => {
console.log("got access token", responseJson);
this.setState({json: responseJson});
if(responseJson.access_token != undefined) { if(responseJson.access_token != undefined) {
this.props.callback(responseJson.user_id, responseJson.access_token, hs); this.props.callback(responseJson.user_id, responseJson.access_token, homeserverUrl);
} else { } else {
this.setState({error: responseJson.error}); this.setState({ error: responseJson.error });
} }
}) }).catch((error) => {
.catch((error) => { /* FIXME: Why are errors being swallowed here? */
console.error(url, error); console.error(error);
}); });
});
}, },
handleUserChange: function(e) { handleUserChange: function(e) {
@ -106,7 +123,7 @@ let login = create({
this.setState({formState: formState}); this.setState({formState: formState});
}, },
handleHsChange: function(e) { handleHomeserverChange: function(e) {
let formState = this.state.formState; let formState = this.state.formState;
formState.hs = e.target.value; formState.hs = e.target.value;
this.setState({formState: formState}); this.setState({formState: formState});
@ -138,9 +155,9 @@ let login = create({
<label htmlFor="hs" className={hsState}>Homeserver: </label> <label htmlFor="hs" className={hsState}>Homeserver: </label>
{this.state.hs.prompt ? ( {this.state.hs.prompt ? (
<> <>
<input type="text" id="hs" value={this.state.formState["hs"]} onChange={this.handleHsChange}/> <input type="text" id="hs" value={this.state.formState["hs"]} onChange={this.handleHomeserverChange}/>
</> </>
) : ( ) : (
<span id="hs">{this.state.formState["hs"]}</span> <span id="hs">{this.state.formState["hs"]}</span>
)} )}
@ -152,39 +169,44 @@ let login = create({
} }
}); });
function getApiServer(hostname) { function getApiServer(parsedUrl) {
/* FIXME: Promise.try */ return Promise.try(() => {
return new Promise((resolve, reject) => { console.log("Checking for api server from mxid", urllib.format(parsedUrl));
console.log("Checking for api server from mxid", urllib.format(hostname));
checkApi(hostname).then(() => { return checkApi(parsedUrl);
// Hostname is a valid api server }).then(() => {
hostname.pathname = ""; // Hostname is a valid api server
resolve(urllib.format(hostname)); return buildUrl(parsedUrl, "");
}).catch(() => { }).catch(() => {
console.log("trying .well-known"); /* FIXME: Error filtering */
tryWellKnown(hostname).then((hostname) => { console.log("trying .well-known");
console.log("got .well-known host", hostname);
resolve(hostname); return Promise.try(() => {
}).catch((_err) => { return tryWellKnown(parsedUrl);
/* FIXME: Error chaining */ }).then((hostname) => {
reject(new Error("Fatal error trying to get API host")); console.log("got .well-known host", hostname);
});
return hostname;
}).catch((_err) => {
/* FIXME: Error chaining */
throw new Error("Fatal error trying to get API host");
}); });
}); });
} }
function checkApi(host) { function checkApi(host) {
let versionUrl = buildUrl(host, "/_matrix/client/versions"); return Promise.try(() => {
return new Promise((resolve, reject) => { let versionUrl = buildUrl(host, "/_matrix/client/versions");
fetch(versionUrl).then((response) => {
return Promise.try(() => {
return fetch(versionUrl);
}).then((response) => {
if (response.status != 200) { if (response.status != 200) {
console.log("Invalid homeserver url", versionUrl); console.log("Invalid homeserver url", versionUrl);
/* FIXME: Error types */ /* FIXME: Error types */
return reject(new Error("Invalid homeserver URL")); throw new Error("Invalid homeserver URL");
} }
resolve();
}).catch((err) => {
reject(err);
}); });
}); });
} }
@ -193,36 +215,43 @@ function tryWellKnown(host) {
let wellKnownUrl = urllib.format(Object.assign(host, { let wellKnownUrl = urllib.format(Object.assign(host, {
pathname: "/.well-known/matrix/client" pathname: "/.well-known/matrix/client"
})); }));
console.log("Trying", wellKnownUrl, "for .well-known"); console.log("Trying", wellKnownUrl, "for .well-known");
return new Promise((resolve, reject) => {
return fetch(wellKnownUrl) return Promise.try(() => {
.then((response) => { return fetch(wellKnownUrl);
if (response.status != 200) { }).tap((response) => {
console.log("no well-known in use"); if (response.status != 200) {
reject(new Error("No homeserver found")); console.log("no well-known in use");
}
return response; /* FIXME: Error type */
}).catch((_error) => { throw new Error("No homeserver found");
/* FIXME: Error chaining */ }
reject(new Error("can't fetch .well-known")); }).catch((_error) => {
}) /* FIXME: Error chaining */
.then((response) => response.json()) throw new Error("can't fetch .well-known");
.then((json) => { }).then((response) => {
console.log("Parsed json", json); return response.json();
if (json['m.homeserver'] != undefined && json['m.homeserver'].base_url != undefined) { }).then((json) => {
resolve(json['m.homeserver'].base_url); console.log("Parsed json", json);
}
}) if (json['m.homeserver'] != null && json['m.homeserver'].base_url != null) {
.catch((err) => { return json['m.homeserver'].base_url;
console.log("Error in json", err); } else {
/* FIXME: Error chaining */ /* FIXME: Error type */
reject(new Error("Error while parsing .well-known")); throw new Error("No homeserver specified in .well-known");
}); }
}).catch((err) => {
/* FIXME: Error filtering? */
console.log("Error in json", err);
/* FIXME: Error chaining */
throw new Error("Error while parsing .well-known");
}); });
} }
function buildUrl(host, path) { function buildUrl(parsedUrl, path) {
return urllib.format(Object.assign(host, { return urllib.format(Object.assign(parsedUrl, {
pathname: path pathname: path
})); }));
} }

@ -96,7 +96,8 @@ let input = create({
uploadFiles: function(uploads) { uploadFiles: function(uploads) {
let client = this.props.client; let client = this.props.client;
Promise.map(uploads, (upload) => {
return Promise.map(uploads, (upload) => {
let fileUploadPromise = client.uploadContent(upload.file, let fileUploadPromise = client.uploadContent(upload.file,
{onlyContentUri: false}).then((response) => { {onlyContentUri: false}).then((response) => {
return response.content_uri; return response.content_uri;
@ -107,28 +108,25 @@ let input = create({
let additionalPromise; let additionalPromise;
if (mimeType.startsWith("image/") || mimeType.startsWith("video/")) { if (mimeType.startsWith("image/") || mimeType.startsWith("video/")) {
function elementToThumbnail(element) { function elementToThumbnail(element) {
/* FIXME: Get rid of `new Promise`, replace with Promise.try */ return Promise.try(() => {
return new Promise((resolve, reject) => { return riot.createThumbnail(element,
riot.createThumbnail(element,
element.width, element.width,
element.height, element.height,
thumbnailType thumbnailType
) );
.catch((error) => { }).catch((error) => {
console.error("neo: error getting thumbnail", error); console.error("neo: error getting thumbnail", error);
reject(error);
})
.then((thumbResult) => {
return client.uploadContent(thumbResult.thumbnail, {onlyContentUri: false});
}).then((response) => {
return resolve({
thumbnail_url: response.content_uri,
thumbnail_info: {
mimetype: thumbnailType
}
});
});
throw error;
}).then((thumbResult) => {
return client.uploadContent(thumbResult.thumbnail, {onlyContentUri: false});
}).then((response) => {
return {
thumbnail_url: response.content_uri,
thumbnail_info: {
mimetype: thumbnailType
}
};
}); });
} }
if (mimeType.startsWith("image/")) { if (mimeType.startsWith("image/")) {
@ -142,6 +140,7 @@ let input = create({
} }
// create and upload thumbnail // create and upload thumbnail
let thumbnailType = "image/png"; let thumbnailType = "image/png";
if (mimeType == "image/jpeg") { if (mimeType == "image/jpeg") {
thumbnailType = mimeType; thumbnailType = mimeType;
} }
@ -150,15 +149,18 @@ let input = create({
} else { } else {
// m.file // m.file
} }
Promise.all([fileUploadPromise, additionalPromise]).then((result) => {
return Promise.all([fileUploadPromise, additionalPromise]).then((result) => {
console.log(result); console.log(result);
let info = { let info = {
mimetype: mimeType mimetype: mimeType
}; };
if (result[1] != undefined) { if (result[1] != undefined) {
info = Object.assign(info, result[1]); info = Object.assign(info, result[1]);
} }
client.sendEvent(this.props.roomId, "m.room.message", {
return client.sendEvent(this.props.roomId, "m.room.message", {
body: upload.file.name, body: upload.file.name,
msgtype: eventType, msgtype: eventType,
info: info, info: info,
@ -213,21 +215,21 @@ let input = create({
render: function() { render: function() {
return <div className="input"> return <div className="input">
{this.props.replyEvent && {this.props.replyEvent &&
<div className="replyEvent" onClick={() => this.props.onReplyClick()}> <div className="replyEvent" onClick={() => this.props.onReplyClick()}>
{this.props.replyEvent.plaintext()} {this.props.replyEvent.plaintext()}
</div> </div>
} }
{this.state.uploads.length > 0 && {this.state.uploads.length > 0 &&
<div className="imgPreview"> <div className="imgPreview">
{this.state.uploads.map((upload, key) => { {this.state.uploads.map((upload, key) => {
return ( return (
<div key={key}> <div key={key}>
<img src={upload.preview}/> <img src={upload.preview}/>
<span onClick={() => this.removeUpload(key)}>X</span> <span onClick={() => this.removeUpload(key)}>X</span>
</div> </div>
); );
})} })}
</div> </div>
} }
<div className="content"> <div className="content">
<textarea ref={this.setRef} rows="1" spellCheck="false" placeholder="unencrypted message"></textarea> <textarea ref={this.setRef} rows="1" spellCheck="false" placeholder="unencrypted message"></textarea>

@ -0,0 +1,27 @@
"use strict";
const Promise = require("bluebird");
module.exports = function createApiRequester(apiUrl) {
return function apiRequest(path, body) {
return Promise.try(() => {
let targetUrl = apiUrl + path;
return Promise.try(() => {
return fetch(targetUrl, {
body: JSON.stringify(body),
headers: {
'content-type': 'application/json'
},
method: 'POST',
});
}).then((response) => {
if (response.status >= 200 && response.status < 400) {
return response.json();
} else {
throw new Error(`Non-200 response code for ${targetUrl}: ${response.status}`);
}
});
});
}
};
Loading…
Cancel
Save