InstaQuiz
Github
  • Introduction
  • Setup
    • Requirements
    • Environment setup
    • Testing
  • Database
    • Schema
    • Setup
  • Server
    • Folder structure
    • Code structure
    • API Reference
  • Client
    • Folder structure
    • Code structure
  • deployment
    • Your own server
    • Fly.io
Powered by GitBook
On this page
  • How it works
  • Telegram communication
  • Themes
  • Routing
  • Components
  • Configs
  • Elements
  • Ready
  • Working with sockets
  • Callbacks
  • Listening to events
  • Reactivity
  1. Client

Code structure

PreviousFolder structureNextYour own server

Last updated 1 year ago

In this guide you will learn how InstaQuiz client works and how you can extend it for your own usage.

How it works

The client uses Vue.Js and Tailwindcss for a good UI experience for the users, axios and Socket.IO client library for communicating with the server.

Vue's state management library is used to manage state around the application each page has it's own "store" that manages state of the page. stores are at ./client/src/stores.

Telegram communication

Communication with Telegram is done through their library for web apps that is injected in the index.html file located in ./client/index.html. you can add below line to your index file's header and use it in your own app.

<!-- index.html -->
<script src="https://telegram.org/js/telegram-web-app.js"></script>

It attaches a Telegram object to window property, in this project ./client/src/types/telegram.d.ts was created to handle types for all variables and functions in the Telegram object. and has detailed explanation about all of them and is up to date with Bot API 6.9 alternatively you can install package for automatically receiving updates to the API with updating it, run below command to install:

pnpm install --save @types/telegram-web-app

Themes

Telegram passes user's main theme colors thorugh css variables and you can use them in your project. in this project it was configured with Tailwindcss's extended themes.

// ./client/tailwind.config.js

/** @type {import('tailwindcss').Config} */
export default {
  content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
  theme: {
    extend: {
      colors: {
        tgBackground: "var(--tg-theme-bg-color)",
        tgText: "var(--tg-theme-text-color)",
        tgHint: "var(--tg-theme-hint-color)",
        tgLink: "var(--tg-theme-link-color)",
        tgButton: "var(--tg-theme-button-color)",
        tgButtonText: "var(--tg-theme-button-text-color)",
        tgSecondaryBackground: "var(--tg-theme-secondary-bg-color)",
      },
    },
  },
  plugins: [],
};

you can use them throughout the web app with tailwind syntax for example a background color will be:

<div class="bg-tgBackground">
...
</div>

Routing

There 3 main pages in the application:

  • HomeView

  • JoinView

  • GameView

Game id for joining is passed through bot's link and start_param query, in router's beforeEach route guard when home page is opened we check if start_param is present and forward the user to JoinView

// ./client/src/router/index.ts

import { createRouter, createWebHistory } from "vue-router";
import HomeView from "../views/HomeView.vue";
import GameView from "../views/GameView.vue";
import JoinView from "../views/JoinView.vue";

const routes = [
  ... // routes
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

router.beforeEach((from, to) => {
  const webApp = window.Telegram.WebApp;
  // Check if app is opened and start_param is present and pass it
  // as game query parameter to JoinView
  if (!to.name && from.name === "home" && webApp.initDataUnsafe.start_param) {
    return {
      name: "join",
      query: {
        game: webApp.initDataUnsafe.start_param,
      },
    };
  }
});

export default router;

Components

To make the code more readable views have been separated in some components in the ./client/src/components folder and they are prefixed with the name of the view.

The ones without a view prefix are used by multiple views.

Configs

./client/src/configs files are for global functions and tools used in the application and it contains code for axios base setup of handling default base url of http server, headers and handling errors which if it was an auth 401 error it will try to close the web app.

And socketConfigs.ts for configuring the connection with the servers socket.

Elements

Ready

// ./client/src/views/HomeView.vue

const webApp = window.Telegram.WebApp;

onMounted(() => {
  webApp.ready();
  webApp.expand();
  // More actions
});

Working with sockets

Callbacks

Each emit request can also have a callback that when the code on the server is done returns a result which is more like a Rest API that can be useful for knowing the result of the operation or receive some extra data.

For the callback to work on the server, the client has to have a callback function as it's last inputted parameter.

An example for a callback in the code:

// ./client/src/stores/joinGameStore.ts

socket.emit(
  "getWaitList",
  gameInfo.value?.id,
  gameInfo.value?.status,
  (response: any) => { // callback function as last parameter
    // response is a json object and contains all data
    if (response.status === 200) {
      // Handle success
    } else {
      // Handle failure
    }
  }
);

Listening to events

Each view can listen to events coming from the socket and update the ui, for example the wait list:

// ./client/src/stores/joinGameStore.ts

socket.on("updateWaitList", (waitList) => {
  // if data exists, update the list of users in wait list
  if (waitList) {
    users.value = waiList;
  }
});

Reactivity

// ./client/src/stores/gameStore.ts

// Return boolean to check if the game is running
// Changes when gameInfo is updated.
const gameIsRunning = computed(() => gameInfo.value?.status === 1);

Remember to use computed values only for UI components, functions in the store do not need reactivity.

One more colorConfigs.ts file is there to get a random tailwindcss color string to use when creating the circle beside the user's name in leaderboard, because when starting the app with menu button or an inline button the user's field will not be sent.

Almost all elements in the application are simple HTML one's except the category selector for the HomeView that is located in ./client/src/components/HomeSelect.vue that uses 's ListBox elements for a feel of still being in Telegram.

When each view is mounted we can call which will tell Telegram that the app is ready to be shown to the user and also .expand() function to expand the page and be able to show more data.

Each client can listens to updates regarding the game they're in with the gameId that was sent from them to the server which joins them in a Socket.IO which is used to assign messages sent from the server to the correct client.

All reactive elements in the web app are handled by VueJs's .

was used for holding reactive data, and for processing new data based on the reactive objects. an example of computed:

Pinia
@types/telegram-web-app
photo_url
headlessui
windows.Telegram.webApp.ready()
room
Reactivity API
ref()
computed()