import { Ref, watch } from "vue"; import { useStorage } from "@vueuse/core"; import { IMember, Member, Poll, Vote, Song, Playlist, Venue } from "../../models/index"; import { uuid } from "common-types"; import { ApiResponse, bearerToken, FetchResponse, isOk } from "./fns/index"; import type { IRequestMagicLink, ISession } from "/~/../functions/models/index"; const token = useStorage("token", null) as Ref; const email = useStorage("email", null) as Ref; watch(token, (v) => document.documentElement.classList.toggle("logged-in", v !== null), { immediate: true, }); /** * Sends a "magic link" to register a given user and/or device */ async function sendMagicLink(email: string): Promise { const body: IRequestMagicLink = { email, referrer: document.referrer, screenWidth: window.screen.width, }; const res = await fetch(`${process.env.API_ROOT}/sendMagicLink`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); return res.ok ? res.json() : res; } /** * Receives a ChallengeToken from a magic link, and if the token is * valid, receives: * * 1. Session token (which will be used in all subsequent API calls) * 2. the Member's _profile_ object * * Magic Link failures originate from: * * - `Expired [408]` - the token was expired * - `Bad Request [400]` - the challenge token couldn't be looked up, * probably an invalid token id * - `Server Error [500]` - unhandled errors */ async function login( newEmail: string, challengeToken: string ): Promise> { const res = await fetch(`${process.env.API_ROOT}/login`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ email: newEmail, token: challengeToken, }), }); if (res.ok) { // set browser's local storage email.value = newEmail; token.value = challengeToken; return { res, payload: await res.json() }; } else { return { res }; } } /** * clears the `token` and `email` values in local storage, * this removes the device's ability to use the API and the * app will treat the device as a logged out user. */ function logout() { email.value = null; token.value = null; } /** * Syncs all models to ensure the IndexedDB is current with * backend data (e.g., google sheet) */ async function syncData() { const res = await fetch(`${process.env.API_ROOT}/syncData`, { method: "GET", headers: { Authentication: `Bearer ${token}`, "Content-Type": "application/json", }, }); if (res.ok) { const payload = await res.json(); console.log({ payload }); } else { console.warn(`problems syncing data [${res.status}]: ${res.statusText}`); } } /** * Allows member to update their own profile */ async function updateProfile(profile: IMember) { // } async function vote(topic: unknown, value: unknown) { // } /** * Adds a new song to the list of songs tracked on the site */ async function addSong(song: unknown) { // } async function addPlaylist(playlist: unknown) { // } /** * Likes a record * * @param what the model which is being liked * @param uuid the `uuid` on the model being liked */ async function like(what: unknown, uuid: uuid) { // } export function useApi() { return { sendMagicLink, syncData, updateProfile, addSong, addPlaylist, bearerToken, login, isOk, }; }