import React, { useState, useContext, createContext, useReducer, useEffect } from "react"; import Integration from "./Integration"; import * as serverApi from '../../api'; import SpotifyClientCreds from "./spotify/SpotifyClientCreds"; import * as backend from "../backend/integrations"; import { handleNotLoggedIn, NotLoggedInError } from "../backend/request"; import { useAuth } from "../useAuth"; import YoutubeMusicWebScraper from "./youtubemusic/YoutubeMusicWebScraper"; export type IntegrationState = { id: number, integration: Integration, properties: serverApi.CreateIntegrationRequest, }; export type IntegrationsState = IntegrationState[] | "Loading"; export function isIntegrationState(v: any): v is IntegrationState { return 'id' in v && 'integration' in v && 'properties' in v; } export interface Integrations { state: IntegrationsState, addIntegration: (v: serverApi.CreateIntegrationRequest) => Promise, deleteIntegration: (id: number) => Promise, modifyIntegration: (id: number, v: serverApi.CreateIntegrationRequest) => Promise, updateFromUpstream: () => Promise, }; export const IntegrationClasses: Record = { [serverApi.IntegrationType.SpotifyClientCredentials]: SpotifyClientCreds, [serverApi.IntegrationType.YoutubeWebScraper]: YoutubeMusicWebScraper, } export function makeDefaultIntegrationProperties(type: serverApi.IntegrationType): serverApi.CreateIntegrationRequest { switch (type) { case serverApi.IntegrationType.SpotifyClientCredentials: { return { name: "Spotify App", type: type, details: { clientId: "" }, secretDetails: { clientSecret: "" }, } } case serverApi.IntegrationType.YoutubeWebScraper: { return { name: "Youtube Music Web Scraper", type: type, details: {}, secretDetails: {}, } } default: { throw new Error("Unimplemented default integration.") } } } export function makeIntegration(p: serverApi.CreateIntegrationRequest, id: number) { switch (p.type) { case serverApi.IntegrationType.SpotifyClientCredentials: { return new SpotifyClientCreds(id); } case serverApi.IntegrationType.YoutubeWebScraper: { return new YoutubeMusicWebScraper(id); } default: { throw new Error("Unimplemented integration type.") } } } const integrationsContext = createContext({ state: [], addIntegration: async () => 0, modifyIntegration: async () => { }, deleteIntegration: async () => { }, updateFromUpstream: async () => { }, }); export function ProvideIntegrations(props: { children: any }) { const integrations = useProvideIntegrations(); return {props.children}; } export const useIntegrations = () => { return useContext(integrationsContext); }; function useProvideIntegrations(): Integrations { let auth = useAuth(); enum IntegrationsActions { SetItem = "SetItem", Set = "Set", DeleteItem = "DeleteItem", AddItem = "AddItem", } let IntegrationsReducer = (state: IntegrationsState, action: any): IntegrationsState => { switch (action.type) { case IntegrationsActions.SetItem: { if (state !== "Loading") { return state.map((item: any) => { return (item.id === action.id) ? action.value : item; }) } return state; } case IntegrationsActions.Set: { return action.value; } case IntegrationsActions.DeleteItem: { if (state !== "Loading") { const newState = [...state]; return newState.filter((item: any) => item.id !== action.id); } return state; } case IntegrationsActions.AddItem: { return [...state, action.value]; } default: throw new Error("Unimplemented Integrations state update.") } } const [state, dispatch] = useReducer(IntegrationsReducer, []) let updateFromUpstream = async () => { return await backend.getIntegrations() .then((integrations: serverApi.ListIntegrationsResponse) => { dispatch({ type: IntegrationsActions.Set, value: integrations.map((i: any) => { return { integration: new (IntegrationClasses[i.type])(i.id), properties: { ...i }, id: i.id, } }) }); }) .catch((e) => handleNotLoggedIn(auth, e)); } let addIntegration = async (v: serverApi.CreateIntegrationRequest) => { const id = await backend.createIntegration(v).catch((e: any) => { handleNotLoggedIn(auth, e) }); await updateFromUpstream(); return id; } let deleteIntegration = async (id: number) => { await backend.deleteIntegration(id).catch((e: any) => { handleNotLoggedIn(auth, e) }); await updateFromUpstream(); } let modifyIntegration = async (id: number, v: serverApi.CreateIntegrationRequest) => { await backend.modifyIntegration(id, v).catch((e: any) => { handleNotLoggedIn(auth, e) }); await updateFromUpstream(); } useEffect(() => { if (auth.user) { updateFromUpstream() } }, [auth]); return { state: state, addIntegration: addIntegration, modifyIntegration: modifyIntegration, deleteIntegration: deleteIntegration, updateFromUpstream: updateFromUpstream, } }