summaryrefslogtreecommitdiff
path: root/rushs/eplace/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'rushs/eplace/src/utils')
-rw-r--r--rushs/eplace/src/utils/auth.js124
-rw-r--r--rushs/eplace/src/utils/notify.js57
-rw-r--r--rushs/eplace/src/utils/rateLimits.js3
-rw-r--r--rushs/eplace/src/utils/streams.js108
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)