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):