diff options
Diffstat (limited to 'rushs/eplace/src/utils')
| -rw-r--r-- | rushs/eplace/src/utils/auth.js | 124 | ||||
| -rw-r--r-- | rushs/eplace/src/utils/notify.js | 57 | ||||
| -rw-r--r-- | rushs/eplace/src/utils/rateLimits.js | 3 | ||||
| -rw-r--r-- | rushs/eplace/src/utils/streams.js | 108 |
4 files changed, 292 insertions, 0 deletions
diff --git a/rushs/eplace/src/utils/auth.js b/rushs/eplace/src/utils/auth.js new file mode 100644 index 0000000..2576282 --- /dev/null +++ b/rushs/eplace/src/utils/auth.js @@ -0,0 +1,124 @@ +// FIXME: This file should handle the authentication +// Exports must include: +// - authedAPIRequest (make an authenticated request to the API) + +/** + * This function makes an authenticated request to the API + * This function must always hit the /api/* endpoint. + * @param {string} endpoint + * @param {object} options This object should at least contain the method. + * For the other options, you can refer to the fetch documentation as it should be the same. + * @returns {Promise<Response>} the response + * We want a {Promise<Response>} so we can read the headers as well as the body, rather than + * just the body. + **/ +export async function authedAPIRequest(endpoint, options) { + if (localStorage.getItem("token")) { + // essaye de contacter l'endpoint + const headers = { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }; + + if (options["headers"]) { + options["headers"]["Authorization"] = + `Bearer ${localStorage.getItem("token")}`; + } else { + options["headers"] = headers; + } + + const apiURL = `${import.meta.env.VITE_URL}/api${endpoint}`; + + try { + const response = await fetch(apiURL, options); + + if (response.status === 200) { + return response; + } else { + console.log("request error"); + console.log(response); + if ( + response.status === 401 && + (await response.json()).message.match(/Token expired/) + ) { + localStorage.removeItem("token"); + if (!(await sendTokenRequest())) { + return null; + } + + const response = await authedAPIRequest(endpoint, options); + + if (!response) { + return null; + } + + return response; + } else { + localStorage.clear(); + // window.location.replace(import.meta.env.VITE_URL); + await sendTokenRequest(); + return null; + } + } + } catch { + console.log("an error occured while fetching"); + return null; + } + } + + await sendTokenRequest(); + return null; +} + +// Functions may include: +// - sendTokenRequest (get a token or refresh it) +export async function sendTokenRequest() { + if ( + !localStorage.getItem("token") && + !localStorage.getItem("refresh_token") + ) { + const authQueryParams = { + client_id: import.meta.env.VITE_CLIENT_ID, + scope: "epita profile picture", + redirect_uri: `${import.meta.env.VITE_URL}/complete/epita/`, + response_type: "code", + }; + const url = new URL(`${import.meta.env.VITE_AUTH_URL}/authorize`); + + //`?client_id=${authQueryParams.client_id}&scope=${authQueryParams.scope}&redirect_uri=${authQueryParams.redirect_uri}&response_type=${authQueryParams.response_type}`, + url.searchParams.append("client_id", authQueryParams.client_id); + url.searchParams.append("scope", authQueryParams.scope); + url.searchParams.append("redirect_uri", authQueryParams.redirect_uri); + url.searchParams.append("response_type", authQueryParams.response_type); + + window.location.replace(url); + return false; + } else if (localStorage.getItem("refresh_token")) { + const form = new FormData(); + + form.append("client_id", import.meta.env.VITE_CLIENT_ID); + form.append( + "redirect_uri", + `${import.meta.env.VITE_URL}/complete/epita/`, + ); + form.append("grant_type", "refresh_token"); + form.append("refresh_token", localStorage.getItem("refresh_token")); + const res = await fetch(`${import.meta.env.VITE_URL}/auth-api/token`, { + method: "POST", + body: form, + }); + + if (res.status === 200) { + const response = await res.json(); + + localStorage.setItem("token", response.id_token); + localStorage.setItem("refresh_token", response.refresh_token); + return true; + } else { + localStorage.clear(); + // window.location.replace(import.meta.env.VITE_URL); + // HERE ptetre return direct senTokenRequest + await sendTokenRequest(); + return false; + } + } +} diff --git a/rushs/eplace/src/utils/notify.js b/rushs/eplace/src/utils/notify.js new file mode 100644 index 0000000..b6ed7dc --- /dev/null +++ b/rushs/eplace/src/utils/notify.js @@ -0,0 +1,57 @@ +import $ from "jquery"; +import alertHtml from "../components/notifications/index.html"; + +const alertContainer = $("#alert-container")?.[0]; + +const iconMap = { + info: "fa-info-circle", + success: "fa-thumbs-up", + warning: "fa-exclamation-triangle", + error: "ffa fa-exclamation-circle", +}; + +/** + * Create an alert + * @param {string} title + * @param {string} message + * @param {string} type - success, warning, error + */ +export const createAlert = (title, message, type) => { + $.ajax({ + url: alertHtml, + success: (data) => { + const [alert] = $(data); + + // Return if the alert cannot be created, usefull when a redirect is made + if (!alertContainer || !alert || !alert.classList) { + return; + } + + // Add the type class to the alert + alert.classList.add( + `Alert${type.charAt(0).toUpperCase() + type.slice(1)}`, + ); + + // Replace values in innerHTML + alert.innerHTML = alert.innerHTML + .replace(/{{title}}/g, title) + .replace(/{{content}}/g, message) + .replace(/{{icon_classes}}/g, iconMap[type]); + + // Get the close button + const closeBtn = alert.getElementsByClassName("AlertClose")?.[0]; + + closeBtn?.addEventListener("click", () => { + alert.remove(); + }); + + // Append the alert to the container + alertContainer.append(alert); + + // Remove the alert after 5 seconds + setTimeout(() => { + alert.remove(); + }, 5000); + }, + }); +}; diff --git a/rushs/eplace/src/utils/rateLimits.js b/rushs/eplace/src/utils/rateLimits.js new file mode 100644 index 0000000..d7b11a6 --- /dev/null +++ b/rushs/eplace/src/utils/rateLimits.js @@ -0,0 +1,3 @@ +// FIXME: This file should handle the rate limits +// Functions may include: +// - displayTimer (util function to display the timer for the rate limit) diff --git a/rushs/eplace/src/utils/streams.js b/rushs/eplace/src/utils/streams.js new file mode 100644 index 0000000..f714527 --- /dev/null +++ b/rushs/eplace/src/utils/streams.js @@ -0,0 +1,108 @@ +// FIXME: This file should handle the sockets and the subscriptions +import { io } from "socket.io-client"; +import { v4 as uuidv4 } from "uuid"; + +import { createAlert } from "./notify"; +import { fetchRoomConfig, joinRoom } from "../rooms"; +import { getCanvas } from "../rooms/canvas"; +import { initCanvas, renderCanvasUpdate } from "../rooms/canvas/utils"; +import { sendTokenRequest } from "../utils/auth"; + +let subscribed = false; + +export let socket = undefined; + +const updateBuffer = []; + +// Exports must include +// - initSocket (initialize the connection to the socket server) +export async function initSocket() { + if (!localStorage.getItem("token")) { + if (!(await sendTokenRequest())) { + createAlert("ForgeID", "Authentication failed", "error"); + return; + } + + createAlert("ForgeID", "Authentication successful", "success"); + } + + if (socket) { + return socket; + } + + socket = io(import.meta.env.VITE_URL, { + extraHeaders: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + socket.on("connect", () => { + joinRoom(); + }); + + socket.on("message", async (msg) => { + if (msg.result && msg.result.type === "started") { + const roomConfig = await fetchRoomConfig("epi-place"); + + if (!roomConfig) { + return; + } + + const pixels = await getCanvas("epi-place"); + + if (!pixels) { + return; + } + + initCanvas(roomConfig, pixels); + // debuffer pixel updates + catchUpOnUpdates(); + subscribed = true; + } + }); + + socket.on("pixel-update", (update) => { + if (!subscribed) { + updateBuffer.push({ + color: update.result.data.json.color, + posX: update.result.data.json.posX, + posY: update.result.data.json.posY, + }); + } else { + renderCanvasUpdate( + update.result.data.json.color, + update.result.data.json.posX, + update.result.data.json.posY, + ); + } + }); + return socket; +} +// - socket (variable resulting of initSocket function) + +// Functions may include: +// - subscribe (subscribe to a room's stream or chat) +export function subscribe(slug) { + const subscription = { + id: uuidv4(), + method: "subscription", + params: { + path: "rooms.canvas.getStream", + input: { + json: { + roomSlug: slug, + }, + }, + }, + }; + + socket.send(subscription); +} +function catchUpOnUpdates() { + while (updateBuffer.length > 0) { + const update = updateBuffer.pop(); + + renderCanvasUpdate(update.color, update.posX, update.posY); + } +} +// - unsubscribe (unsubscribe from a room's stream or chat) +// - sendMessage (send a message to a room's chat) |
