Merge branch 'main' into e2e

This commit is contained in:
Roland Osborne 2022-12-07 10:30:42 -08:00
commit 32e2479793
17 changed files with 232 additions and 22 deletions

51
.design/DESIGN.md Normal file
View File

@ -0,0 +1,51 @@
# Databag Design Guidelines & Resources
A collection of design contribution guidelines and resources for our Databag product.
> **All participating designers are highly encouraged to shape and evolve these guidelines!**
## Welcome
Databag is a product targeted for the decentralized web community. It is a federeated messenger that gives people back their privacy and control of their data.
## How to contribute design
1. Check out open [issues](https://github.com/balzack/databag/issues) here on GitHub (we label them with `design: required`)
2. Feel free to open an issue on your own if you find something you would like to contribute to the project and use the `design: idea` label for it.
3. There are no existing figma files yet, for now create new ones and share them publicly
4. Add your contributions to an issue and we promise we will review your contribution carefully and foster discussions
**We encourage you to:**
- Get in touch with the team by starting a discussion on [GitHub](https://github.com/balzack/databag/discussions).
## Target audience
The initial users are typically very technical, but hopefully this product will be embedded into consumer electronics and reach a much less technical audience. The design may benefit by exposing some technical attributes. For example, with a self-hosted product the users my be interested in knowing where their data actually lives.
## Design relevant materials
Here is a list of design relevant information and materials:
### Fonts
Currently we're using Roboto everywhere.
### Colors
We're using shades of green deliberately staying away from the corporate blue. The current background color is (#8fbea7) with the primary button color (#448866).
### Logos
We don't really have a logo yet, and are currently just using a scaled image from the app as the [icon](https://github.com/balzack/databag/blob/main/doc/icon.png)
### Design Files, Screenshots, etc
We don't have any design files yet. Initially you can reference screenshots for the [browser app](https://github.com/balzack/databag/blob/main/doc/browser.png) and [mobile app](https://github.com/balzack/databag/blob/main/doc/mobile.png)
## License
All design work is licensed under the
[Apache-2.0](https://github.com/balzack/databag/blob/main/LICENSE)
[(Back to top)](#-table-of-contents)

View File

@ -1,3 +1,6 @@
[![contribute.design](https://contribute.design/api/shield/balzack/databag)](https://contribute.design/balzack/databag)
<div align="center">
<a href="#"><img src="/doc/icon.png" width="8%" style="border-radius:50%"></a>
<h3 align="center">Databag</h3>
@ -19,7 +22,7 @@
Databag is a self-hosted messaging service. Notable features include:
- Public-private key based identity (not bound to any blockchain or hosting domain)
- Federated (accounts on different nodes can communicate)
- Topic based threads (messages grouped by topic not contacts)
- Topic based threads (messages organized by topic not contacts)
- Lightweight (server runs on a raspberry pi zero v1.3)
- Decentralized (direct communication between app and contact's node)
- Low latency (use of websockets for push events to avoid polling)
@ -30,10 +33,10 @@ Databag is a self-hosted messaging service. Notable features include:
<br>
<p align="center">
<a href="https://apps.apple.com/us/app/databag/id6443741428">
<img src="/doc/astore.png" width="15%">
<img src="/doc/astore.png" width="18%">
</a>
<a href="https://play.google.com/store/apps/details?id=com.databag">
<img src="/doc/gplay.png" width="15%">
<img src="/doc/gplay.png" width="18%">
</a>
</p>
@ -43,7 +46,10 @@ The app is available in the google and apple stores. You can also test out the p
To use databag, you will need a DNS name pointing to your node with a certificate. You can deploy a node manually, but you will have a much easier time using a container service. Containers for arm64 and amd64 are available [here](https://hub.docker.com/r/balzack/databag/tags).
Instruction for installing without a container on a Raspberry Pi Zero are [here](/doc/pizero.md).
### Docker Compose Command
From the net/container sub directory:
- sudo docker-compose -f compose.yaml -p databag up
### Example with Portainer and Nginx Proxy Manager
@ -71,3 +77,9 @@ From Your Browser:
- Click 'Save'
- Click the user icon to generate a new account link
- Follow the link to create an account
### Other installation options
Instruction for installing without a container on a Raspberry Pi Zero are [here](/doc/pizero.md).
Instruction for installing without a container in AWS are [here](/doc/aws.md).

View File

@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.4</string>
<string>1.5</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

View File

@ -69,6 +69,9 @@ export function Admin() {
</View>
)}
</View>
<View style={styles.version}>
<Text style={styles.versiontext}>v{ state.version }</Text>
</View>
</View>
</KeyboardAvoidingView>
);

View File

@ -108,6 +108,13 @@ export const styles = StyleSheet.create({
nologintext: {
color: Colors.disabled,
},
version: {
display: 'flex',
alignItems: 'flex-end',
},
versiontext: {
color: Colors.grey,
fontSize: 14,
},
})

View File

@ -17,12 +17,17 @@ export function useAdmin() {
server: null,
token: null,
plainText: false,
version: null,
});
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
useEffect(() => {
updateState({ version: app.state.version });
}, [app]);
const checkStatus = async () => {
try {
updateState({ unclaimed: status });

View File

@ -20,6 +20,8 @@ export function useAppContext() {
loginTimestamp: null,
disconnected: null,
deviceToken: null,
loggingOut: false,
version: getVersion(),
});
const store = useContext(StoreContext);
const account = useContext(AccountContext);
@ -27,6 +29,7 @@ export function useAppContext() {
const card = useContext(CardContext);
const channel = useContext(ChannelContext);
const count = useRef(0);
const delay = useRef(0);
const ws = useRef(null);
@ -83,7 +86,7 @@ export function useAppContext() {
username: getUsername,
create: async (server, username, password, token) => {
await addAccount(server, username, password, token);
const access = await setLogin(username, server, password, getApplicatioName(), getVersion(), getDeviceId(), state.deviceToken, notifications)
const access = await setLogin(username, server, password, getApplicationName(), getVersion(), getDeviceId(), state.deviceToken, notifications)
await store.actions.setSession({ ...access, server});
await setSession({ ...access, server });
if (access.pushSupported) {
@ -108,6 +111,7 @@ export function useAppContext() {
}
},
logout: async () => {
updateState({ loggingOut: true });
try {
await messaging().deleteToken();
const token = await messaging().getToken();
@ -119,6 +123,7 @@ export function useAppContext() {
}
await clearSession();
await store.actions.clearSession();
updateState({ loggingOut: false });
},
remove: async () => {
await removeProfile(state.server, state.appToken);
@ -131,6 +136,7 @@ export function useAppContext() {
clearWebsocket();
ws.current = new WebSocket(`wss://${server}/status`);
ws.current.onmessage = (ev) => {
delay.current = 0;
try {
const rev = JSON.parse(ev.data);
try {
@ -159,9 +165,10 @@ export function useAppContext() {
ws.current.onclose = () => {}
ws.current.onopen = () => {}
ws.current.onerror = () => {}
delay.current = 1;
setWebsocket(server, token);
}
}, 1000)
}, 1000 * delay.current)
}
ws.current.onopen = () => {
ws.current.send(JSON.stringify({ AppToken: token }))

View File

@ -1,5 +1,4 @@
import { Keyboard, KeyboardAvoidingView, ActivityIndicator, Modal, Platform, TextInput, View, TouchableOpacity, Text, } from 'react-native';
import { useKeepAwake } from 'expo-keep-awake';
import { FlatList, ScrollView } from '@stream-io/flat-list-mvcp';
import { memo, useState, useRef, useEffect } from 'react';
import { useConversation } from './useConversation.hook';
@ -59,8 +58,6 @@ export function ConversationBody() {
};
}, []);
useKeepAwake();
const latch = () => {
if (!state.momentum) {
actions.latch();

View File

@ -1,5 +1,6 @@
import { Image, View, Text, TouchableOpacity } from 'react-native';
import { useRef } from 'react';
import { useKeepAwake } from 'expo-keep-awake';
import Colors from 'constants/Colors';
import { Video, AVPlaybackStatus } from 'expo-av';
import { useAudioAsset } from './useAudioAsset.hook';
@ -12,6 +13,8 @@ export function AudioAsset({ topicId, asset, dismiss }) {
const { state, actions } = useAudioAsset(topicId, asset);
useKeepAwake();
const player = useRef(null);
return (

View File

@ -1,5 +1,6 @@
import { ActivityIndicator, Image, View, TouchableOpacity } from 'react-native';
import Colors from 'constants/Colors';
import { useKeepAwake } from 'expo-keep-awake';
import { Video, AVPlaybackStatus } from 'expo-av';
import { useVideoAsset } from './useVideoAsset.hook';
import { styles } from './VideoAsset.styled';
@ -9,6 +10,8 @@ export function VideoAsset({ topicId, asset, dismiss }) {
const { state, actions } = useVideoAsset(topicId, asset);
useKeepAwake();
return (
<View style={styles.container}>
<TouchableOpacity activeOpacity={1} style={styles.container} onPress={actions.showControls}>

View File

@ -1,5 +1,5 @@
import { useEffect, useContext } from 'react';
import { KeyboardAvoidingView, Modal, Alert, TextInput, ScrollView, View, Switch, TouchableOpacity, Text } from 'react-native';
import { ActivityIndicator, KeyboardAvoidingView, Modal, Alert, TextInput, ScrollView, View, Switch, TouchableOpacity, Text } from 'react-native';
import { styles } from './Profile.styled';
import { useProfile } from './useProfile.hook';
import Ionicons from '@expo/vector-icons/AntDesign';
@ -21,9 +21,16 @@ export function Profile({ navigation }) {
</View>
),
headerRight: () => (
<TouchableOpacity style={styles.action} onPress={logout} onLongPress={actions.showDelete}>
<Ionicons name="logout" size={22} color={Colors.primary} />
</TouchableOpacity>
<>
{ state.loggingOut && (
<ActivityIndicator style={styles.action} size="small" />
)}
{ !state.loggingOut && (
<TouchableOpacity style={styles.action} onPress={logout} onLongPress={actions.showDelete}>
<Ionicons name="logout" size={22} color={Colors.primary} />
</TouchableOpacity>
)}
</>
),
});
}
@ -71,9 +78,14 @@ export function Profile({ navigation }) {
<SafeAreaView style={styles.drawer} edges={['top', 'bottom', 'right']}>
<View style={styles.header}>
<Text style={styles.headerText} numberOfLines={1}>{ `${state.handle}@${state.node}` }</Text>
<TouchableOpacity onPress={logout} onLongPress={actions.showDelete}>
<Ionicons name="logout" size={16} color={Colors.grey} />
</TouchableOpacity>
{ state.loggingOut && (
<ActivityIndicator size="small" />
)}
{ !state.loggingOut && (
<TouchableOpacity onPress={logout} onLongPress={actions.showDelete}>
<Ionicons name="logout" size={16} color={Colors.grey} />
</TouchableOpacity>
)}
</View>
<ProfileBody />
<TouchableOpacity style={styles.erase} activeOpacity={1} onPress={actions.showDelete}>

View File

@ -44,6 +44,7 @@ export const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'flex-end',
justifyContent: 'center',
height: 32,
},
headerText: {
paddingLeft: 16,

View File

@ -14,6 +14,7 @@ export function useProfile() {
showDelete: false,
tabbed: null,
confirmDelete: null,
logginOut: false,
});
const app = useContext(AppContext);
@ -42,8 +43,8 @@ export function useProfile() {
}, [profile]);
useEffect(() => {
const { disconnected } = app.state;
updateState({ disconnected });
const { disconnected, loggingOut } = app.state;
updateState({ disconnected, loggingOut });
}, [app]);
const actions = {

1
doc/CHANGELOG.md Normal file
View File

@ -0,0 +1 @@

97
doc/aws.md Normal file
View File

@ -0,0 +1,97 @@
# Install Databag in AWS
These instructions assume you have the following setup:
- an AMD64 Ubuntu EC2 instance with incoming ports 443 and 80<br/>
- an EFS instance<br/>
- a domain name pointing the the IP of your EC2 instance<br/>
## Step 1: obtain cert
sudo apt-get install certbot<br/>
sudo certbot certonly --standalone -d [dns name]<br/>
## Step 2: install databag dependencies
sudo apt-get -y install ffmpeg<br/>
sudo apt-get -y install curl<br/>
sudo apt-get -y install net-tools<br/>
sudo apt-get -y install jq<br/>
sudo apt-get -y install netcat<br/>
sudo apt-get -y install unzip<br/>
sudo apt-get -y install wget<br/>
sudo apt-get -y install git<br/>
sudo apt-get -y install vim<br/>
sudo apt-get -y install fail2ban<br/>
sudo apt-get -y install imagemagick-6.q16<br/>
sudo apt-get -y install build-essential<br/>
sudo apt-get -y install sqlite3<br/>
sudo apt-get -y install openssh-client<br/>
apt-get -y install npm<br/>
apt-get -y upgrade<br/>
npm install --global yarn<br/>
npm install -g n<br/>
n stable<br/>
## Step 3: download and install golang
wget https://go.dev/dl/go1.19.3.linux-amd64.tar.gz<br/>
sudo tar -C /usr/local -xzf go1.19.3.linux-amd64.tar.gz<br/>
## Step 4: clone and build the server
mkdir /app<br/>
cd /app<br/>
git clone https://github.com/balzack/databag.git<br/>
cd /app/databag/net/web<br/>
yarn config set network-timeout 300000<br/>
yarn --cwd /app/databag/net/web install<br/>
yarn --cwd /app/databag/net/web build<br/>
cd /app/databag/net/server<br/>
/usr/local/go/bin/go build databag<br/>
## Step 5: setup databag paths
mkdir -p /var/lib/databag/assets<br/>
mkdir -p /opt/databag/transform<br/>
cp /app/databag/net/container/transform/* /opt/databag/transform/<br/>
## Step 6: mount EFS to store assets
sudo apt-get update<br/>
sudo apt-get -y install git binutils<br/>
git clone https://github.com/aws/efs-utils<br/>
cd efs-utils<br/>
./build-deb.sh<br/>
sudo apt-get -y install ./build/amazon-efs-utils*deb<br/>
sudo mount -t efs file-system-id /var/lib/databag/assets<br/>
## Step 7: initialize the internal datbase
sqlite3 /var/lib/databag/databag.db "VACUUM;"<br/>
sqlite3 /var/lib/databag/databag.db "CREATE TABLE IF NOT EXISTS 'configs' ('id' integer NOT NULL UNIQUE,'config_id' text NOT NULL,'str_value' text,'num_value' integer,'bool_value' numeric,'bin_value' blob,PRIMARY KEY ('id'));"<br/>
sqlite3 /var/lib/databag/databag.db "CREATE UNIQUE INDEX IF NOT EXISTS 'idx_configs_config_id' ON 'configs'('config_id');"<br/>
sqlite3 /var/lib/databag/databag.db "insert into configs (config_id, str_value) values ('asset_path', '/var/lib/databag/assets');"<br/>
sqlite3 /var/lib/databag/databag.db "insert into configs (config_id, str_value) values ('script_path', '/opt/databag/transform/');"<br/>
## Step 8: launch the server
cd /app/databag/net/server<br/>
nohup nice -n -5 /usr/local/go/bin/go run databag [dns name] &<br/>
## Step 9: configure the server
Open your brower to https://[dns name]<br/>
Click the 'cog' in the upper right<br/>
Set an admin password<br/>
Select the 'cog' to bring up the settings modal<br/>
- set your hostname as [dns name]<br/>
- set the key to RSA 2048<br/>
- enable push notifications<br/>
- enable images<br/>
- disable audio<br/>
- disable video<br/>
## Step 10: create accounts
Still in the admin dashboard<br/>
Click the 'add-user' button<br/>
Open the link in a new tab<br/>
Set a username and password<br/>
Setup your profile<br/>
Connect with contacts on other federated instances<br/>
## Step 11: host for your friends and family
Back in the admin dashboard<br/>
Click the 'add-user' and send the link to anyone you want to host<br/>

View File

@ -0,0 +1,10 @@
version: "3.9"
services:
databag:
container_name: databag
image: balzack/databag:latest
ports:
- "7000:7000"
volumes:
- ./databag-data:/data

View File

@ -21,11 +21,11 @@ func main() {
args := os.Args
if len(args) == 3 {
port := ":" + args[2]
path := "etc/letsencrypt/live/" + args[1]
path := "/etc/letsencrypt/live/" + args[1]
log.Printf("starting server at: " + path + " " + port);
log.Fatal(http.ListenAndServeTLS(port, path + "/fullchain.pem", path + "/privkey.pem", handlers.CORS(origins, methods)(router)))
} else if len(args) == 2 {
path := "etc/letsencrypt/live/" + args[1]
path := "/etc/letsencrypt/live/" + args[1]
log.Printf("starting server at: " + path);
log.Fatal(http.ListenAndServeTLS(":443", path + "/fullchain.pem", path + "/privkey.pem", handlers.CORS(origins, methods)(router)))
} else {