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({
client: client
});
this.startClient(client);
},
updateRooms: function (client) {
let rooms = {};
client.getRooms().forEach((room) => {
rooms[room.roomId] = room;
});
this.setState({rooms: rooms});
},
startClient: function(client) {
console.log(client);
client.on("sync", (state, _prevState, _data) => {
if (state == "ERROR") {
/* FIXME: Implement? */
} else if (state == "SYNCING") {
let rooms = {};
client.getRooms().forEach((room) => {
rooms[room.roomId] = room;
});
this.setState({rooms: rooms});
this.updateRooms(client);
} else if (state == "PREPARED") {
/* FIXME: Implement? */
}
});
client.on("Room.localEchoUpdated", (_event) => {
let rooms = {};
client.getRooms().forEach((room) => {
rooms[room.roomId] = room;
});
this.setState({rooms: rooms});
this.updateRooms(client);
});
client.startClient();
@ -87,13 +91,14 @@ let App = create({
if (this.state.client == undefined) {
//Login screen
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 urllib = require('url');
const createApiRequester = require("../lib/api-request");
let login = create({
displayName: "Login",
@ -24,67 +26,82 @@ let login = create({
},
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) {
return this.doLogin();
}
this.setState({
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(':');
if (parts.length != 2) {
return this.setState({error: "Please enter a full mxid, like username:homeserver.tld"});
}
return this.doLogin();
}).catch((error) => {
/* FIXME: Error filtering */
console.log("ERROR fetching homeserver url", error);
let hostname = urllib.parse("https://" + parts[1]);
getApiServer(hostname).then((hs) => {
console.log("Using API server", hs);
let formState = this.state.formState;
formState.user = parts[0];
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});
this.setState({
hs: Object.assign(this.state.hs, {
error: error,
valid: false,
prompt: true
})
});
});
});
},
doLogin: function() {
console.log("Logging in");
let user = this.state.formState.user.replace('@', '');
let password = this.state.formState.pass;
let hs = this.state.apiUrl;
let data = {
user: user,
password: password,
type: "m.login.password",
initial_device_display_name: "Neo v4",
};
return Promise.try(() => {
console.log("Logging in");
let user = this.state.formState.user.replace('@', '');
let password = this.state.formState.pass;
let homeserverUrl = this.state.apiUrl;
return Promise.try(() => {
return this.state.apiRequest("/_matrix/client/r0/login", {
user: user,
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) {
this.props.callback(responseJson.user_id, responseJson.access_token, hs);
this.props.callback(responseJson.user_id, responseJson.access_token, homeserverUrl);
} else {
this.setState({error: responseJson.error});
this.setState({ error: responseJson.error });
}
})
.catch((error) => {
console.error(url, error);
}).catch((error) => {
/* FIXME: Why are errors being swallowed here? */
console.error(error);
});
});
},
handleUserChange: function(e) {
@ -106,7 +123,7 @@ let login = create({
this.setState({formState: formState});
},
handleHsChange: function(e) {
handleHomeserverChange: function(e) {
let formState = this.state.formState;
formState.hs = e.target.value;
this.setState({formState: formState});
@ -138,9 +155,9 @@ let login = create({
<label htmlFor="hs" className={hsState}>Homeserver: </label>
{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>
)}
@ -152,39 +169,44 @@ let login = create({
}
});
function getApiServer(hostname) {
/* FIXME: Promise.try */
return new Promise((resolve, reject) => {
console.log("Checking for api server from mxid", urllib.format(hostname));
checkApi(hostname).then(() => {
// Hostname is a valid api server
hostname.pathname = "";
resolve(urllib.format(hostname));
}).catch(() => {
console.log("trying .well-known");
tryWellKnown(hostname).then((hostname) => {
console.log("got .well-known host", hostname);
resolve(hostname);
}).catch((_err) => {
/* FIXME: Error chaining */
reject(new Error("Fatal error trying to get API host"));
});
function getApiServer(parsedUrl) {
return Promise.try(() => {
console.log("Checking for api server from mxid", urllib.format(parsedUrl));
return checkApi(parsedUrl);
}).then(() => {
// Hostname is a valid api server
return buildUrl(parsedUrl, "");
}).catch(() => {
/* FIXME: Error filtering */
console.log("trying .well-known");
return Promise.try(() => {
return tryWellKnown(parsedUrl);
}).then((hostname) => {
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) {
let versionUrl = buildUrl(host, "/_matrix/client/versions");
return new Promise((resolve, reject) => {
fetch(versionUrl).then((response) => {
return Promise.try(() => {
let versionUrl = buildUrl(host, "/_matrix/client/versions");
return Promise.try(() => {
return fetch(versionUrl);
}).then((response) => {
if (response.status != 200) {
console.log("Invalid homeserver url", versionUrl);
/* 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, {
pathname: "/.well-known/matrix/client"
}));
console.log("Trying", wellKnownUrl, "for .well-known");
return new Promise((resolve, reject) => {
return fetch(wellKnownUrl)
.then((response) => {
if (response.status != 200) {
console.log("no well-known in use");
reject(new Error("No homeserver found"));
}
return response;
}).catch((_error) => {
/* FIXME: Error chaining */
reject(new Error("can't fetch .well-known"));
})
.then((response) => response.json())
.then((json) => {
console.log("Parsed json", json);
if (json['m.homeserver'] != undefined && json['m.homeserver'].base_url != undefined) {
resolve(json['m.homeserver'].base_url);
}
})
.catch((err) => {
console.log("Error in json", err);
/* FIXME: Error chaining */
reject(new Error("Error while parsing .well-known"));
});
return Promise.try(() => {
return fetch(wellKnownUrl);
}).tap((response) => {
if (response.status != 200) {
console.log("no well-known in use");
/* FIXME: Error type */
throw new Error("No homeserver found");
}
}).catch((_error) => {
/* FIXME: Error chaining */
throw new Error("can't fetch .well-known");
}).then((response) => {
return response.json();
}).then((json) => {
console.log("Parsed json", json);
if (json['m.homeserver'] != null && json['m.homeserver'].base_url != null) {
return json['m.homeserver'].base_url;
} else {
/* FIXME: Error type */
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) {
return urllib.format(Object.assign(host, {
function buildUrl(parsedUrl, path) {
return urllib.format(Object.assign(parsedUrl, {
pathname: path
}));
}

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