This project is a chat application built with the Svelte framework and using Gun.js as a decentralized graph database. The application allows users to sign up, log in, send, and receive messages in real-time With decentralized chat experience using the Svelte framework and Gun.js for real-time.
`
## Project Structure
āāā package-lock.json
āāā package.json
āāā public
ā āāā 404.html
ā āāā favicon.png
ā āāā global.css
ā āāā index.html
āāā rollup.config.js
āāā src
āāā App.svelte
āāā Chat.svelte
āāā ChatMessage.svelte
āāā Header.svelte
āāā Login.svelte
āāā main.js
āāā user.js
----------------------------------
+---------------------+
| User Interactions |
+---------------------+
|
v
+--------------------+ +--------------------+
| App Initialization |<---| main.js |
+--------------------+ +--------------------+
|
v
+--------------------+ +--------------------+
| App Component |<---| App.svelte |
+--------------------+ +--------------------+
|
v
+--------------------+ +--------------------+
| Header Component |<---| Header.svelte |
+--------------------+ +--------------------+
|
v
+--------------------+ +--------------------+
| Chat Component |<---| Chat.svelte |
+--------------------+ +--------------------+
|
v
+--------------------+ +--------------------+
| Message Component |<---| ChatMessage.svelte |
+--------------------+ +--------------------+
|
v
+--------------------+ +--------------------+
| User Authentication |<--| user.js |
+--------------------+ +--------------------+
|
v
+--------------------+
| Database (Gun.js) |
+--------------------+
`
package.json and package-lock.json
public/
404.html
, favicon.png
, global.css
, and index.html
which are served to the client.rollup.config.js
src/main.js
src/user.js
import GUN from 'gun';
import 'gun/sea';
import 'gun/axe';
import { writable } from 'svelte/store';
export const db = GUN();
export const user = db.user().recall({ sessionStorage: true });
export const username = writable('');
user.get('alias').on(v => username.set(v));
db.on('auth', async (event) => {
const alias = await user.get('alias');
username.set(alias);
console.log(`signed in as ${alias}`);
});
<script>
import Chat from './Chat.svelte';
import Header from './Header.svelte';
</script>
<div class="app">
<Header />
<Chat />
</div>
<script>
import Login from './Login.svelte';
import ChatMessage from './ChatMessage.svelte';
import { onMount } from 'svelte';
import { username, user } from './user';
import debounce from 'lodash.debounce';
import GUN from 'gun';
const db = GUN();
let newMessage;
let messages = [];
let scrollBottom;
let lastScrollTop;
let canAutoScroll = true;
let unreadMessages = false;
function autoScroll() {
setTimeout(() => scrollBottom?.scrollIntoView({ behavior: 'auto' }), 50);
unreadMessages = false;
}
function watchScroll(e) {
canAutoScroll = (e.target.scrollTop || Infinity) > lastScrollTop;
lastScrollTop = e.target.scrollTop;
}
$: debouncedWatchScroll = debounce(watchScroll, 1000);
onMount(() => {
var match = {
'.': { '>': new Date(+new Date() - 1 * 1000 * 60 * 60 * 3).toISOString() }, '-': 1,
};
db.get('chativanappostolov')
.map(match)
.once(async (data, id) => {
if (data) {
const key = '#foo';
var message = {
who: await db.user(data).get('alias'),
what: (await SEA.decrypt(data.what, key)) + '',
when: GUN.state.is(data, 'what'),
};
if (message.what) {
messages = [...messages.slice(-100), message].sort((a, b) => a.when - b.when);
if (canAutoScroll) {
autoScroll();
} else {
unreadMessages = true;
}
}
}
});
});
async function sendMessage() {
const secret = await SEA.encrypt(newMessage, '#foo');
const message = user.get('all').set({ what: secret });
const index = new Date().toISOString();
db.get('chativanappostolov').get(index).put(message);
newMessage = '';
canAutoScroll = true;
autoScroll();
}
</script>
<div class="container">
{#if $username}
<main on:scroll={debouncedWatchScroll}>
{#each messages as message (message.when)}
<ChatMessage {message} sender={$username} />
{/each}
<div class="dummy" bind:this={scrollBottom} />
</main>
<form on:submit|preventDefault={sendMessage}>
<input type="text" placeholder="Type a message..." bind:value={newMessage} maxlength="100" />
<button type="submit" disabled={!newMessage}>Send</button>
</form>
{:else}
<main>
<Login />
</main>
{/if}
</div>
<script>
export let message;
export let sender;
const messageClass = message.who === sender ? 'sent' : 'received';
const avatar = `https://avatars.dicebear.com/api/initials/${message.who}.svg`;
const ts = new Date(message.when);
</script>
<div class={`message ${messageClass}`}>
<img src={avatar} alt="avatar" />
<div class="message-text">
<p>{message.what}</p>
<time>{ts.toLocaleTimeString()}</time>
</div>
</div>
<script>
import { username, user } from './user';
function signout() {
user.leave();
username.set('');
}
</script>
<header>
<h1>MychatApp</h1>
{#if $username}
<div class="user-bio">
<span><strong>{$username}</strong></span>
<img src={`https://avatars.dicebear.com/api/initials/${$username}.svg`} alt="avatar" />
</div>
<button class="signout-button" on:click={signout}>Sign Out</button>
{/if}
</header>
<script>
import { user } from './user';
let username;
let password;
function login() {
user.auth(username, password, ({ err }) => err && alert(err));
}
function signup() {
user.create(username, password, ({ err }) => {
if (err) {
alert(err);
} else {
login();
}
});
}
</script>
<label for="username">Username</label>
<input name="username" bind:value={username} minlength="3" maxlength="16" />
<label for="password">Password</label>
<input name="password" bind:value={password} type="password" />
<button class="login" on:click={login}>Login</button>
<button class="login" on:click={signup}>Sign Up</button>
+---------------------+
| User Interactions |
+---------------------+
|
v
+--------------------+ +--------------------+
| App Initialization |<---| main.js |
+--------------------+ +--------------------+
|
v
+--------------------+ +--------------------+
| App Component |<---| App.svelte |
+--------------------+ +--------------------+
|
v
+--------------------+ +--------------------+
| Header Component |<---| Header.svelte |
+--------------------+ +--------------------+
|
v
+--------------------+ +--------------------+
| Chat Component |<---| Chat.svelte |
+--------------------+ +--------------------+
|
v
+--------------------+ +--------------------+
| Message Component |<---| ChatMessage.svelte |
+--------------------+ +--------------------+
|
v
+--------------------+ +--------------------+
| User Authentication |<--| user.js |
+--------------------+ +--------------------+
|
v
+--------------------+
| Database (Gun.js) |
+--------------------+
Initialization (main.js):
Root Component (App.svelte):
Header Component (Header.svelte):
Chat Component (Chat.svelte):
onMount
lifecycle function to load messages from the database and set up listeners for new messages.Message Component (ChatMessage.svelte):
User Authentication (user.js):
db
) to recall the user session and update the username store upon authentication.Login Component (Login.svelte):